omniauth_callbacks_controller_spec.rb 8.7 KB
Newer Older
G
gfyoung 已提交
1 2
# frozen_string_literal: true

3 4
require 'spec_helper'

5
describe OmniauthCallbacksController, type: :controller do
6 7
  include LoginHelpers

8 9
  describe 'omniauth' do
    let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) }
10

T
Tiago Botelho 已提交
11
    before do
12
      @original_env_config_omniauth_auth = mock_auth_hash(provider.to_s, +extern_uid, user.email)
13
      stub_omniauth_provider(provider, context: request)
14
    end
15

16 17 18 19
    after do
      Rails.application.env_config['omniauth.auth'] = @original_env_config_omniauth_auth
    end

20 21
    context 'when the user is on the last sign in attempt' do
      let(:extern_uid) { 'my-uid' }
22

23 24 25
      before do
        user.update(failed_attempts: User.maximum_attempts.pred)
        subject.response = ActionDispatch::Response.new
26
      end
27

28 29
      context 'when using a form based provider' do
        let(:provider) { :ldap }
30

31 32 33
        it 'locks the user when sign in fails' do
          allow(subject).to receive(:params).and_return(ActionController::Parameters.new(username: user.username))
          request.env['omniauth.error.strategy'] = OmniAuth::Strategies::LDAP.new(nil)
34

35
          subject.send(:failure)
T
Tiago Botelho 已提交
36

37 38
          expect(user.reload).to be_access_locked
        end
39
      end
40

41 42
      context 'when using a button based provider' do
        let(:provider) { :github }
43

44 45
        it 'does not lock the user when sign in fails' do
          request.env['omniauth.error.strategy'] = OmniAuth::Strategies::GitHub.new(nil)
46

47
          subject.send(:failure)
48

49
          expect(user.reload).not_to be_access_locked
T
Tiago Botelho 已提交
50
        end
51
      end
52
    end
53

54 55 56 57 58 59 60 61 62 63
    context 'when sign in fails' do
      include RoutesHelpers

      let(:extern_uid) { 'my-uid' }
      let(:provider) { :saml }

      def stub_route_as(path)
        allow(@routes).to receive(:generate_extras) { [path, []] }
      end

64
      it 'calls through to the failure handler' do
65 66 67 68 69 70 71 72 73 74 75 76
        request.env['omniauth.error'] = OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch")
        request.env['omniauth.error.strategy'] = OmniAuth::Strategies::SAML.new(nil)
        stub_route_as('/users/auth/saml/callback')

        ForgeryProtection.with_forgery_protection do
          post :failure
        end

        expect(flash[:alert]).to match(/Fingerprint mismatch/)
      end
    end

77 78 79 80 81 82 83 84 85 86
    context 'when a redirect fragment is provided' do
      let(:provider) { :jwt }
      let(:extern_uid) { 'my-uid' }

      before do
        request.env['omniauth.params'] = { 'redirect_fragment' => 'L101' }
      end

      context 'when a redirect url is stored' do
        it 'redirects with fragment' do
J
Jasper Maes 已提交
87
          post provider, session: { user_return_to: '/fake/url' }
88 89 90 91 92 93 94

          expect(response).to redirect_to('/fake/url#L101')
        end
      end

      context 'when a redirect url with a fragment is stored' do
        it 'redirects with the new fragment' do
J
Jasper Maes 已提交
95
          post provider, session: { user_return_to: '/fake/url#replaceme' }
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110

          expect(response).to redirect_to('/fake/url#L101')
        end
      end

      context 'when no redirect url is stored' do
        it 'does not redirect with the fragment' do
          post provider

          expect(response.redirect?).to be true
          expect(response.location).not_to include('#L101')
        end
      end
    end

111 112 113 114
    context 'strategies' do
      context 'github' do
        let(:extern_uid) { 'my-uid' }
        let(:provider) { :github }
T
Tiago Botelho 已提交
115

116
        it 'allows sign in' do
T
Tiago Botelho 已提交
117
          post provider
118

T
Tiago Botelho 已提交
119 120
          expect(request.env['warden']).to be_authenticated
        end
121

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
        context 'when user has no linked provider' do
          let(:user) { create(:user) }

          before do
            sign_in user
          end

          it 'links identity' do
            expect do
              post provider
              user.reload
            end.to change { user.identities.count }.by(1)
          end

          context 'and is not allowed to link the provider' do
            before do
              allow_any_instance_of(IdentityProviderPolicy).to receive(:can?).with(:link).and_return(false)
            end

            it 'returns 403' do
              post provider

              expect(response).to have_gitlab_http_status(403)
            end
          end
        end

149
        shared_context 'sign_up' do
G
gfyoung 已提交
150
          let(:user) { double(email: +'new@example.com') }
151

152 153 154
          before do
            stub_omniauth_setting(block_auto_created_users: false)
          end
155
        end
T
Tiago Botelho 已提交
156

157 158
        context 'sign up' do
          include_context 'sign_up'
T
Tiago Botelho 已提交
159

160 161
          it 'is allowed' do
            post provider
T
Tiago Botelho 已提交
162

163 164
            expect(request.env['warden']).to be_authenticated
          end
T
Tiago Botelho 已提交
165 166
        end

167 168 169 170 171 172
        context 'when OAuth is disabled' do
          before do
            stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
            settings = Gitlab::CurrentSettings.current_application_settings
            settings.update(disabled_oauth_sign_in_sources: [provider.to_s])
          end
T
Tiago Botelho 已提交
173

174
          it 'prevents login via POST' do
T
Tiago Botelho 已提交
175 176 177 178
            post provider

            expect(request.env['warden']).not_to be_authenticated
          end
179 180 181 182 183 184 185 186 187

          it 'shows warning when attempting login' do
            post provider

            expect(response).to redirect_to new_user_session_path
            expect(flash[:alert]).to eq('Signing in using GitHub has been disabled')
          end

          it 'allows linking the disabled provider' do
188
            user.identities.destroy_all # rubocop: disable DestroyAll
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
            sign_in(user)

            expect { post provider }.to change { user.reload.identities.count }.by(1)
          end

          context 'sign up' do
            include_context 'sign_up'

            it 'is prevented' do
              post provider

              expect(request.env['warden']).not_to be_authenticated
            end
          end
        end
      end

      context 'auth0' do
        let(:extern_uid) { '' }
        let(:provider) { :auth0 }

        it 'does not allow sign in without extern_uid' do
          post 'auth0'

          expect(request.env['warden']).not_to be_authenticated
          expect(response.status).to eq(302)
          expect(controller).to set_flash[:alert].to('Wrong extern UID provided. Make sure Auth0 is configured correctly.')
T
Tiago Botelho 已提交
216
        end
217 218
      end
    end
219 220 221
  end

  describe '#saml' do
222
    let(:last_request_id) { 'ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685' }
223 224 225 226
    let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml') }
    let(:mock_saml_response) { File.read('spec/fixtures/authentication/saml_response.xml') }
    let(:saml_config) { mock_saml_config_with_upstream_two_factor_authn_contexts }

227 228 229 230
    def stub_last_request_id(id)
      session['last_authn_request_id'] = id
    end

231
    before do
232
      stub_last_request_id(last_request_id)
233 234
      stub_omniauth_saml_config({ enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
                                  providers: [saml_config] })
G
gfyoung 已提交
235
      mock_auth_hash_with_saml_xml('saml', +'my-uid', user.email, mock_saml_response)
236
      request.env['devise.mapping'] = Devise.mappings[:user]
237 238
      request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth']
    end
239

240 241 242 243 244 245 246 247
    context 'with GitLab initiated request' do
      before do
        post :saml, params: { SAMLResponse: mock_saml_response }
      end

      context 'when worth two factors' do
        let(:mock_saml_response) do
          File.read('spec/fixtures/authentication/saml_response.xml')
248
            .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN')
249 250 251 252 253
        end

        it 'expects user to be signed_in' do
          expect(request.env['warden']).to be_authenticated
        end
254
      end
255

256 257 258 259 260
      context 'when not worth two factors' do
        it 'expects user to provide second factor' do
          expect(response).to render_template('devise/sessions/two_factor')
          expect(request.env['warden']).not_to be_authenticated
        end
261 262
      end
    end
263

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
    context 'with IdP initiated request' do
      let(:user) { create(:user) }
      let(:last_request_id) { '99999' }

      before do
        sign_in user
      end

      it 'lets the user know their account isn\'t linked yet' do
        post :saml, params: { SAMLResponse: mock_saml_response }

        expect(flash[:notice]).to eq 'Request to link SAML account must be authorized'
      end

      it 'redirects to profile account page' do
        post :saml, params: { SAMLResponse: mock_saml_response }

        expect(response).to redirect_to(profile_account_path)
      end

      it 'doesn\'t link a new identity to the user' do
        expect { post :saml, params: { SAMLResponse: mock_saml_response } }.not_to change { user.identities.count }
T
Tiago Botelho 已提交
286
      end
287 288
    end
  end
289
end