login_spec.rb 22.3 KB
Newer Older
1 2
require 'spec_helper'

3
describe 'Login' do
4
  include TermsHelper
5
  include UserLoginHelper
6

7
  before do
8
    stub_authentication_activity_metrics(debug: true)
9 10
  end

11 12 13 14
  describe 'password reset token after successful sign in' do
    it 'invalidates password reset token' do
      expect(authentication_metrics)
        .to increment(:user_authenticated_counter)
15

16
      user = create(:user)
17

18
      expect(user.reset_password_token).to be_nil
19

20 21 22
      visit new_user_password_path
      fill_in 'user_email', with: user.email
      click_button 'Reset password'
23

24 25
      user.reload
      expect(user.reset_password_token).not_to be_nil
26

27 28 29 30 31 32 33
      find('a[href="#login-pane"]').click
      gitlab_sign_in(user)
      expect(current_path).to eq root_path

      user.reload
      expect(user.reset_password_token).to be_nil
    end
34 35
  end

36 37
  describe 'initial login after setup' do
    it 'allows the initial admin to create a password' do
38 39 40
      expect(authentication_metrics)
        .to increment(:user_authenticated_counter)

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
      # This behavior is dependent on there only being one user
      User.delete_all

      user = create(:admin, password_automatically_set: true)

      visit root_path
      expect(current_path).to eq edit_user_password_path
      expect(page).to have_content('Please create a password for your new account.')

      fill_in 'user_password',              with: 'password'
      fill_in 'user_password_confirmation', with: 'password'
      click_button 'Change your password'

      expect(current_path).to eq new_user_session_path
      expect(page).to have_content(I18n.t('devise.passwords.updated_not_active'))

      fill_in 'user_login',    with: user.username
      fill_in 'user_password', with: 'password'
      click_button 'Sign in'

      expect(current_path).to eq root_path
    end
63 64 65 66 67

    it 'does not show flash messages when login page' do
      visit root_path
      expect(page).not_to have_content('You need to sign in or sign up before continuing.')
    end
68 69
  end

70 71
  describe 'with a blocked account' do
    it 'prevents the user from logging in' do
72 73 74 75
      expect(authentication_metrics)
        .to increment(:user_blocked_counter)
        .and increment(:user_unauthenticated_counter)
        .and increment(:user_session_destroyed_counter).twice
76

77 78
      user = create(:user, :blocked)

79
      gitlab_sign_in(user)
80 81 82 83

      expect(page).to have_content('Your account has been blocked.')
    end

84
    it 'does not update Devise trackable attributes', :clean_gitlab_redis_shared_state do
85 86 87 88 89
      expect(authentication_metrics)
        .to increment(:user_blocked_counter)
        .and increment(:user_unauthenticated_counter)
        .and increment(:user_session_destroyed_counter).twice

90 91
      user = create(:user, :blocked)

92
      expect { gitlab_sign_in(user) }.not_to change { user.reload.sign_in_count }
93 94 95
    end
  end

96 97
  describe 'with the ghost user' do
    it 'disallows login' do
98 99
      expect(authentication_metrics)
        .to increment(:user_unauthenticated_counter)
100
        .and increment(:user_password_invalid_counter)
101

102
      gitlab_sign_in(User.ghost)
103 104 105 106

      expect(page).to have_content('Invalid Login or password.')
    end

107
    it 'does not update Devise trackable attributes', :clean_gitlab_redis_shared_state do
108 109 110 111 112 113
      expect(authentication_metrics)
        .to increment(:user_unauthenticated_counter)
        .and increment(:user_password_invalid_counter)

      expect { gitlab_sign_in(User.ghost) }
        .not_to change { User.ghost.reload.sign_in_count }
114 115 116
    end
  end

117
  describe 'with two-factor authentication' do
118
    def enter_code(code)
119
      fill_in 'user_otp_attempt', with: code
120 121 122
      click_button 'Verify code'
    end

123
    context 'with valid username/password' do
124 125
      let(:user) { create(:user, :two_factor) }

126
      before do
127
        gitlab_sign_in(user, remember: true)
128

129
        expect(page).to have_content('Two-Factor Authentication')
130 131
      end

132
      it 'does not show a "You are already signed in." error message' do
133 134 135 136 137
        expect(authentication_metrics)
          .to increment(:user_authenticated_counter)
          .and increment(:user_session_override_counter)
          .and increment(:user_two_factor_authenticated_counter)

138
        enter_code(user.current_otp)
139

140 141 142
        expect(page).not_to have_content('You are already signed in.')
      end

143 144
      context 'using one-time code' do
        it 'allows login with valid code' do
145 146 147 148 149
          expect(authentication_metrics)
            .to increment(:user_authenticated_counter)
            .and increment(:user_session_override_counter)
            .and increment(:user_two_factor_authenticated_counter)

150
          enter_code(user.current_otp)
151

152 153 154
          expect(current_path).to eq root_path
        end

155 156 157 158 159 160
        it 'persists remember_me value via hidden field' do
          field = first('input#user_remember_me', visible: false)

          expect(field.value).to eq '1'
        end

161
        it 'blocks login with invalid code' do
162
          # TODO invalid 2FA code does not generate any events
163
          # See gitlab-org/gitlab-ce#49785
164

165
          enter_code('foo')
166

167 168
          expect(page).to have_content('Invalid two-factor code')
        end
169 170

        it 'allows login with invalid code, then valid code' do
171 172 173 174 175
          expect(authentication_metrics)
            .to increment(:user_authenticated_counter)
            .and increment(:user_session_override_counter)
            .and increment(:user_two_factor_authenticated_counter)

176 177 178 179 180 181
          enter_code('foo')
          expect(page).to have_content('Invalid two-factor code')

          enter_code(user.current_otp)
          expect(current_path).to eq root_path
        end
182 183 184 185 186 187
      end

      context 'using backup code' do
        let(:codes) { user.generate_otp_backup_codes! }

        before do
188
          expect(codes.size).to eq 10
189

190
          # Ensure the generated codes get saved
191 192 193 194 195
          user.save
        end

        context 'with valid code' do
          it 'allows login' do
196 197 198 199 200
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)
              .and increment(:user_session_override_counter)
              .and increment(:user_two_factor_authenticated_counter)

201
            enter_code(codes.sample)
202

203 204 205 206
            expect(current_path).to eq root_path
          end

          it 'invalidates the used code' do
207 208 209 210 211
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)
              .and increment(:user_session_override_counter)
              .and increment(:user_two_factor_authenticated_counter)

212 213
            expect { enter_code(codes.sample) }
              .to change { user.reload.otp_backup_codes.size }.by(-1)
214
          end
215 216

          it 'invalidates backup codes twice in a row' do
217 218 219 220 221 222
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter).twice
              .and increment(:user_session_override_counter).twice
              .and increment(:user_two_factor_authenticated_counter).twice
              .and increment(:user_session_destroyed_counter)

223 224 225 226 227 228 229 230 231 232
            random_code = codes.delete(codes.sample)
            expect { enter_code(random_code) }
              .to change { user.reload.otp_backup_codes.size }.by(-1)

            gitlab_sign_out
            gitlab_sign_in(user)

            expect { enter_code(codes.sample) }
              .to change { user.reload.otp_backup_codes.size }.by(-1)
          end
233 234 235 236
        end

        context 'with invalid code' do
          it 'blocks login' do
237
            # TODO, invalid two factor authentication does not increment
238
            # metrics / counters, see gitlab-org/gitlab-ce#49785
239

240 241
            code = codes.sample
            expect(user.invalidate_otp_backup_code!(code)).to eq true
242

243
            user.save!
244
            expect(user.reload.otp_backup_codes.size).to eq 9
245 246

            enter_code(code)
247
            expect(page).to have_content('Invalid two-factor code.')
248 249 250 251
          end
        end
      end
    end
252

253
    context 'when logging in via OAuth' do
254 255 256 257
      let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')}
      let(:mock_saml_response) do
        File.read('spec/fixtures/authentication/saml_response.xml')
      end
258

259 260 261 262 263 264 265 266
      before do
        stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
                                  providers: [mock_saml_config_with_upstream_two_factor_authn_contexts])
      end

      context 'when authn_context is worth two factors' do
        let(:mock_saml_response) do
          File.read('spec/fixtures/authentication/saml_response.xml')
267 268
              .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
                    'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS')
269 270 271
        end

        it 'signs user in without prompting for second factor' do
272 273
          # TODO, OAuth authentication does not fire events,
          # see gitlab-org/gitlab-ce#49786
274 275 276 277 278 279 280

          expect(authentication_metrics)
            .to increment(:user_authenticated_counter)
            .and increment(:user_session_override_counter)

          sign_in_using_saml!

281 282 283 284 285
          expect(page).not_to have_content('Two-Factor Authentication')
          expect(current_path).to eq root_path
        end
      end

286
      context 'when two factor authentication is required' do
287
        it 'shows 2FA prompt after OAuth login' do
288 289 290 291 292 293 294
          expect(authentication_metrics)
            .to increment(:user_authenticated_counter)
            .and increment(:user_session_override_counter)
            .and increment(:user_two_factor_authenticated_counter)

          sign_in_using_saml!

295
          expect(page).to have_content('Two-Factor Authentication')
296

297
          enter_code(user.current_otp)
298

299 300
          expect(current_path).to eq root_path
        end
301
      end
302 303 304 305

      def sign_in_using_saml!
        gitlab_sign_in_via('saml', user, 'my-uid', mock_saml_response)
      end
306
    end
307 308
  end

309
  describe 'without two-factor authentication' do
310 311
    context 'with correct username and password' do
      let(:user) { create(:user) }
312

313 314 315
      it 'allows basic login' do
        expect(authentication_metrics)
          .to increment(:user_authenticated_counter)
316

317 318 319 320 321
        gitlab_sign_in(user)

        expect(current_path).to eq root_path
        expect(page).not_to have_content('You are already signed in.')
      end
322 323
    end

324 325
    context 'with invalid username and password' do
      let(:user) { create(:user, password: 'not-the-default') }
326

327 328 329 330 331 332 333 334 335
      it 'blocks invalid login' do
        expect(authentication_metrics)
          .to increment(:user_unauthenticated_counter)
          .and increment(:user_password_invalid_counter)

        gitlab_sign_in(user)

        expect(page).to have_content('Invalid Login or password.')
      end
336
    end
337
  end
338 339 340

  describe 'with required two-factor authentication enabled' do
    let(:user) { create(:user) }
341
    #  TODO: otp_grace_period_started_at
342

343
    context 'global setting' do
344 345 346
      before do
        stub_application_setting(require_two_factor_authentication: true)
      end
347

348
      context 'with grace period defined' do
349
        before do
350
          stub_application_setting(two_factor_grace_period: 48)
351 352
        end

353 354
        context 'within the grace period' do
          it 'redirects to two-factor configuration page' do
355 356 357 358 359
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)

            gitlab_sign_in(user)

360 361 362
            expect(current_path).to eq profile_two_factor_auth_path
            expect(page).to have_content('The global settings require you to enable Two-Factor Authentication for your account. You need to do this before ')
          end
363

364
          it 'allows skipping two-factor configuration', :js do
365 366
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)
367

368 369 370
            gitlab_sign_in(user)

            expect(current_path).to eq profile_two_factor_auth_path
371 372 373
            click_link 'Configure it later'
            expect(current_path).to eq root_path
          end
374 375
        end

376 377
        context 'after the grace period' do
          let(:user) { create(:user, otp_grace_period_started_at: 9999.hours.ago) }
378

379
          it 'redirects to two-factor configuration page' do
380 381 382 383 384
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)

            gitlab_sign_in(user)

385 386 387 388 389 390
            expect(current_path).to eq profile_two_factor_auth_path
            expect(page).to have_content(
              'The global settings require you to enable Two-Factor Authentication for your account.'
            )
          end

391
          it 'disallows skipping two-factor configuration', :js do
392 393 394 395 396
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)

            gitlab_sign_in(user)

397 398 399 400 401 402 403
            expect(current_path).to eq profile_two_factor_auth_path
            expect(page).not_to have_link('Configure it later')
          end
        end
      end

      context 'without grace period defined' do
404
        before do
405
          stub_application_setting(two_factor_grace_period: 0)
406 407
        end

408
        it 'redirects to two-factor configuration page' do
409 410 411 412 413
          expect(authentication_metrics)
            .to increment(:user_authenticated_counter)

          gitlab_sign_in(user)

414
          expect(current_path).to eq profile_two_factor_auth_path
415 416 417
          expect(page).to have_content(
            'The global settings require you to enable Two-Factor Authentication for your account.'
          )
418 419 420 421
        end
      end
    end

422 423 424 425 426 427
    context 'group setting' do
      before do
        group1 = create :group, name: 'Group 1', require_two_factor_authentication: true
        group1.add_user(user, GroupMember::DEVELOPER)
        group2 = create :group, name: 'Group 2', require_two_factor_authentication: true
        group2.add_user(user, GroupMember::DEVELOPER)
428 429
      end

430
      context 'with grace period defined' do
431
        before do
432 433 434 435 436
          stub_application_setting(two_factor_grace_period: 48)
        end

        context 'within the grace period' do
          it 'redirects to two-factor configuration page' do
437 438 439 440 441 442 443 444 445 446 447 448 449
            Timecop.freeze do
              expect(authentication_metrics)
                .to increment(:user_authenticated_counter)

              gitlab_sign_in(user)

              expect(current_path).to eq profile_two_factor_auth_path
              expect(page).to have_content(
                'The group settings for Group 1 and Group 2 require you to enable '\
                'Two-Factor Authentication for your account. '\
                'You can leave Group 1 and leave Group 2. '\
                'You need to do this '\
                'before '\
450
                "#{(Time.zone.now + 2.days).strftime("%a, %d %b %Y %H:%M:%S %z")}"
451 452
              )
            end
453 454
          end

455
          it 'allows skipping two-factor configuration', :js do
456 457 458 459
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)

            gitlab_sign_in(user)
460

461
            expect(current_path).to eq profile_two_factor_auth_path
462 463 464 465 466 467 468 469 470
            click_link 'Configure it later'
            expect(current_path).to eq root_path
          end
        end

        context 'after the grace period' do
          let(:user) { create(:user, otp_grace_period_started_at: 9999.hours.ago) }

          it 'redirects to two-factor configuration page' do
471 472 473 474 475
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)

            gitlab_sign_in(user)

476 477 478 479 480 481 482
            expect(current_path).to eq profile_two_factor_auth_path
            expect(page).to have_content(
              'The group settings for Group 1 and Group 2 require you to enable ' \
              'Two-Factor Authentication for your account.'
            )
          end

483
          it 'disallows skipping two-factor configuration', :js do
484 485 486 487 488
            expect(authentication_metrics)
              .to increment(:user_authenticated_counter)

            gitlab_sign_in(user)

489 490 491 492 493 494 495
            expect(current_path).to eq profile_two_factor_auth_path
            expect(page).not_to have_link('Configure it later')
          end
        end
      end

      context 'without grace period defined' do
496
        before do
497 498 499 500
          stub_application_setting(two_factor_grace_period: 0)
        end

        it 'redirects to two-factor configuration page' do
501 502 503 504 505
          expect(authentication_metrics)
            .to increment(:user_authenticated_counter)

          gitlab_sign_in(user)

506 507 508
          expect(current_path).to eq profile_two_factor_auth_path
          expect(page).to have_content(
            'The group settings for Group 1 and Group 2 require you to enable ' \
509 510
            'Two-Factor Authentication for your account. '\
            'You can leave Group 1 and leave Group 2.'
511 512
          )
        end
513 514 515
      end
    end
  end
516 517 518

  describe 'UI tabs and panes' do
    context 'when no defaults are changed' do
519
      it 'correctly renders tabs and panes' do
520 521 522 523 524 525 526 527
        ensure_tab_pane_correctness
      end
    end

    context 'when signup is disabled' do
      before do
        stub_application_setting(signup_enabled: false)
      end
528 529

      it 'correctly renders tabs and panes' do
530 531 532 533 534 535 536 537 538 539 540
        ensure_tab_pane_correctness
      end
    end

    context 'when ldap is enabled' do
      before do
        visit new_user_session_path
        allow(page).to receive(:form_based_providers).and_return([:ldapmain])
        allow(page).to receive(:ldap_enabled).and_return(true)
      end

541
      it 'correctly renders tabs and panes' do
542 543 544 545 546 547 548 549 550 551 552
        ensure_tab_pane_correctness(false)
      end
    end

    context 'when crowd is enabled' do
      before do
        visit new_user_session_path
        allow(page).to receive(:form_based_providers).and_return([:crowd])
        allow(page).to receive(:crowd_enabled?).and_return(true)
      end

553
      it 'correctly renders tabs and panes' do
554 555 556 557
        ensure_tab_pane_correctness(false)
      end
    end
  end
558 559 560 561 562 563 564 565 566

  context 'when terms are enforced' do
    let(:user) { create(:user) }

    before do
      enforce_terms
    end

    it 'asks to accept the terms on first login' do
567 568 569
      expect(authentication_metrics)
        .to increment(:user_authenticated_counter)

570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
      visit new_user_session_path

      fill_in 'user_login', with: user.email
      fill_in 'user_password', with: '12345678'

      click_button 'Sign in'

      expect_to_be_on_terms_page

      click_button 'Accept terms'

      expect(current_path).to eq(root_path)
      expect(page).not_to have_content('You are already signed in.')
    end

    it 'does not ask for terms when the user already accepted them' do
586 587 588
      expect(authentication_metrics)
        .to increment(:user_authenticated_counter)

589 590 591 592 593 594 595 596 597 598 599
      accept_terms(user)

      visit new_user_session_path

      fill_in 'user_login', with: user.email
      fill_in 'user_password', with: '12345678'

      click_button 'Sign in'

      expect(current_path).to eq(root_path)
    end
600 601 602 603 604 605 606 607 608

    context 'when 2FA is required for the user' do
      before do
        group = create(:group, require_two_factor_authentication: true)
        group.add_developer(user)
      end

      context 'when the user did not enable 2FA' do
        it 'asks to set 2FA before asking to accept the terms' do
609 610 611
          expect(authentication_metrics)
            .to increment(:user_authenticated_counter)

612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
          visit new_user_session_path

          fill_in 'user_login', with: user.email
          fill_in 'user_password', with: '12345678'

          click_button 'Sign in'

          expect_to_be_on_terms_page
          click_button 'Accept terms'

          expect(current_path).to eq(profile_two_factor_auth_path)

          fill_in 'pin_code', with: user.reload.current_otp

          click_button 'Register with two-factor app'
          click_link 'Proceed'

          expect(current_path).to eq(profile_account_path)
        end
      end

      context 'when the user already enabled 2FA' do
        before do
          user.update!(otp_required_for_login: true,
                       otp_secret:  User.generate_otp_secret(32))
        end

        it 'asks the user to accept the terms' do
640 641 642 643 644
          expect(authentication_metrics)
            .to increment(:user_authenticated_counter)
            .and increment(:user_session_override_counter)
            .and increment(:user_two_factor_authenticated_counter)

645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
          visit new_user_session_path

          fill_in 'user_login', with: user.email
          fill_in 'user_password', with: '12345678'
          click_button 'Sign in'

          fill_in 'user_otp_attempt', with: user.reload.current_otp
          click_button 'Verify code'

          expect_to_be_on_terms_page
          click_button 'Accept terms'

          expect(current_path).to eq(root_path)
        end
      end
    end

    context 'when the users password is expired' do
      before do
        user.update!(password_expires_at: Time.parse('2018-05-08 11:29:46 UTC'))
      end

      it 'asks the user to accept the terms before setting a new password' do
668 669 670
        expect(authentication_metrics)
          .to increment(:user_authenticated_counter)

671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
        visit new_user_session_path

        fill_in 'user_login', with: user.email
        fill_in 'user_password', with: '12345678'
        click_button 'Sign in'

        expect_to_be_on_terms_page
        click_button 'Accept terms'

        expect(current_path).to eq(new_profile_password_path)

        fill_in 'user_current_password', with: '12345678'
        fill_in 'user_password', with: 'new password'
        fill_in 'user_password_confirmation', with: 'new password'
        click_button 'Set new password'

        expect(page).to have_content('Password successfully changed')
      end
    end

    context 'when the user does not have an email configured' do
      let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml', email: 'temp-email-for-oauth-user@gitlab.localhost') }

      before do
        stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
      end

      it 'asks the user to accept the terms before setting an email' do
699 700 701 702
        expect(authentication_metrics)
          .to increment(:user_authenticated_counter)
          .and increment(:user_session_override_counter)

703 704 705 706 707 708 709 710 711 712 713 714 715 716
        gitlab_sign_in_via('saml', user, 'my-uid')

        expect_to_be_on_terms_page
        click_button 'Accept terms'

        expect(current_path).to eq(profile_path)

        fill_in 'Email', with: 'hello@world.com'

        click_button 'Update profile settings'

        expect(page).to have_content('Profile was successfully updated')
      end
    end
717
  end
718
end