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

3
feature 'Login', feature: true do
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
  describe 'initial login after setup' do
    it 'allows the initial admin to create a password' do
      # 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
  end

30
  describe 'with two-factor authentication' do
31
    def enter_code(code)
32
      fill_in 'user_otp_attempt', with: code
33 34 35
      click_button 'Verify code'
    end

36
    context 'with valid username/password' do
37 38
      let(:user) { create(:user, :two_factor) }

39
      before do
40
        login_with(user, remember: true)
41
        expect(page).to have_content('Two-Factor Authentication')
42 43
      end

44 45 46 47 48
      it 'does not show a "You are already signed in." error message' do
        enter_code(user.current_otp)
        expect(page).not_to have_content('You are already signed in.')
      end

49 50 51 52 53 54
      context 'using one-time code' do
        it 'allows login with valid code' do
          enter_code(user.current_otp)
          expect(current_path).to eq root_path
        end

55 56 57 58 59 60
        it 'persists remember_me value via hidden field' do
          field = first('input#user_remember_me', visible: false)

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

61 62 63 64
        it 'blocks login with invalid code' do
          enter_code('foo')
          expect(page).to have_content('Invalid two-factor code')
        end
65 66 67 68 69 70 71 72

        it 'allows login with invalid code, then valid code' do
          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
73 74 75 76 77 78
      end

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

        before do
79
          expect(codes.size).to eq 10
80

81
          # Ensure the generated codes get saved
82 83 84 85 86 87 88 89 90 91
          user.save
        end

        context 'with valid code' do
          it 'allows login' do
            enter_code(codes.sample)
            expect(current_path).to eq root_path
          end

          it 'invalidates the used code' do
92 93
            expect { enter_code(codes.sample) }.
              to change { user.reload.otp_backup_codes.size }.by(-1)
94 95 96 97 98 99 100
          end
        end

        context 'with invalid code' do
          it 'blocks login' do
            code = codes.sample
            expect(user.invalidate_otp_backup_code!(code)).to eq true
101

102
            user.save!
103
            expect(user.reload.otp_backup_codes.size).to eq 9
104 105

            enter_code(code)
106
            expect(page).to have_content('Invalid two-factor code.')
107 108 109 110
          end
        end
      end
    end
111 112

    context 'logging in via OAuth' do
113 114 115 116 117 118 119 120 121 122
      def saml_config
        OpenStruct.new(name: 'saml', label: 'saml', args: {
          assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
          idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
          idp_sso_target_url: 'https://idp.example.com/sso/saml',
          issuer: 'https://localhost:3443/',
          name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
        })
      end

123
      def stub_omniauth_config(messages)
124 125 126 127 128 129
        Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
        Rails.application.routes.disable_clear_and_finalize = true
        Rails.application.routes.draw do
          post '/users/auth/saml' => 'omniauth_callbacks#saml'
        end
        allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
130
        allow(Gitlab.config.omniauth).to receive_messages(messages)
131
        expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
132 133
      end

134
      it 'shows 2FA prompt after OAuth login' do
135
        stub_omniauth_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config])
136
        user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')
137 138 139 140 141 142 143
        login_via('saml', user, 'my-uid')

        expect(page).to have_content('Two-Factor Authentication')
        enter_code(user.current_otp)
        expect(current_path).to eq root_path
      end
    end
144 145
  end

146
  describe 'without two-factor authentication' do
147 148
    let(:user) { create(:user) }

149 150 151 152
    it 'allows basic login' do
      login_with(user)
      expect(current_path).to eq root_path
    end
153 154 155 156 157 158 159 160 161 162

    it 'does not show a "You are already signed in." error message' do
      login_with(user)
      expect(page).not_to have_content('You are already signed in.')
    end

    it 'blocks invalid login' do
      user = create(:user, password: 'not-the-default')

      login_with(user)
163
      expect(page).to have_content('Invalid Login or password.')
164
    end
165
  end
166 167 168 169 170 171 172 173 174 175 176 177 178

  describe 'with required two-factor authentication enabled' do
    let(:user) { create(:user) }
    before(:each) { stub_application_setting(require_two_factor_authentication: true) }

    context 'with grace period defined' do
      before(:each) do
        stub_application_setting(two_factor_grace_period: 48)
        login_with(user)
      end

      context 'within the grace period' do
        it 'redirects to two-factor configuration page' do
179 180
          expect(current_path).to eq profile_two_factor_auth_path
          expect(page).to have_content('You must enable Two-Factor Authentication for your account before')
181 182
        end

183 184
        it 'allows skipping two-factor configuration', js: true do
          expect(current_path).to eq profile_two_factor_auth_path
185 186 187 188 189 190 191 192 193 194

          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
195 196
          expect(current_path).to eq profile_two_factor_auth_path
          expect(page).to have_content('You must enable Two-Factor Authentication for your account.')
197 198
        end

199 200
        it 'disallows skipping two-factor configuration', js: true do
          expect(current_path).to eq profile_two_factor_auth_path
201 202 203 204 205
          expect(page).not_to have_link('Configure it later')
        end
      end
    end

206
    context 'without grace period defined' do
207 208 209 210 211 212
      before(:each) do
        stub_application_setting(two_factor_grace_period: 0)
        login_with(user)
      end

      it 'redirects to two-factor configuration page' do
213 214
        expect(current_path).to eq profile_two_factor_auth_path
        expect(page).to have_content('You must enable Two-Factor Authentication for your account.')
215 216 217
      end
    end
  end
218 219 220

  describe 'UI tabs and panes' do
    context 'when no defaults are changed' do
221
      it 'correctly renders tabs and panes' do
222 223 224 225 226 227 228 229
        ensure_tab_pane_correctness
      end
    end

    context 'when signup is disabled' do
      before do
        stub_application_setting(signup_enabled: false)
      end
230 231

      it 'correctly renders tabs and panes' do
232 233 234 235 236 237 238 239 240 241 242
        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

243
      it 'correctly renders tabs and panes' do
244 245 246 247 248 249 250 251 252 253 254
        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

255
      it 'correctly renders tabs and panes' do
256 257 258 259
        ensure_tab_pane_correctness(false)
      end
    end

Jacob Schatz's avatar
Jacob Schatz committed
260
    def ensure_tab_pane_correctness(visit_path = true)
261 262 263
      if visit_path
        visit new_user_session_path
      end
264

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
      ensure_tab_pane_counts
      ensure_one_active_tab
      ensure_one_active_pane
    end

    def ensure_tab_pane_counts
      tabs_count = page.all('[role="tab"]').size
      expect(page).to have_selector('[role="tabpanel"]', count: tabs_count)
    end

    def ensure_one_active_tab
      expect(page).to have_selector('.nav-tabs > li.active', count: 1)
    end

    def ensure_one_active_pane
      expect(page).to have_selector('.tab-pane.active', count: 1)
    end
  end
283
end