1# frozen_string_literal: true 2 3require 'spec_helper' 4 5RSpec.describe OmniauthCallbacksController, type: :controller do 6 include LoginHelpers 7 8 describe 'omniauth' do 9 let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) } 10 let(:additional_info) { {} } 11 12 before do 13 @original_env_config_omniauth_auth = mock_auth_hash(provider.to_s, extern_uid, user.email, additional_info: additional_info ) 14 stub_omniauth_provider(provider, context: request) 15 end 16 17 after do 18 Rails.application.env_config['omniauth.auth'] = @original_env_config_omniauth_auth 19 end 20 21 context 'a deactivated user' do 22 let(:provider) { :github } 23 let(:extern_uid) { 'my-uid' } 24 25 before do 26 user.deactivate! 27 post provider 28 end 29 30 it 'allows sign in' do 31 expect(request.env['warden']).to be_authenticated 32 end 33 34 it 'activates the user' do 35 expect(user.reload.active?).to be_truthy 36 end 37 38 it 'shows reactivation flash message after logging in' do 39 expect(flash[:notice]).to eq('Welcome back! Your account had been deactivated due to inactivity but is now reactivated.') 40 end 41 end 42 43 context 'when sign in is not valid' do 44 let(:provider) { :github } 45 let(:extern_uid) { 'my-uid' } 46 47 it 'renders omniauth error page' do 48 allow_next_instance_of(Gitlab::Auth::OAuth::User) do |instance| 49 allow(instance).to receive(:valid_sign_in?).and_return(false) 50 end 51 52 post provider 53 54 expect(response).to render_template("errors/omniauth_error") 55 expect(response).to have_gitlab_http_status(:unprocessable_entity) 56 end 57 end 58 59 context 'when the user is on the last sign in attempt' do 60 let(:extern_uid) { 'my-uid' } 61 62 before do 63 user.update!(failed_attempts: User.maximum_attempts.pred) 64 subject.response = ActionDispatch::Response.new 65 end 66 67 context 'when using a form based provider' do 68 let(:provider) { :ldap } 69 70 it 'locks the user when sign in fails' do 71 allow(subject).to receive(:params).and_return(ActionController::Parameters.new(username: user.username)) 72 request.env['omniauth.error.strategy'] = OmniAuth::Strategies::LDAP.new(nil) 73 74 subject.send(:failure) 75 76 expect(user.reload).to be_access_locked 77 end 78 end 79 80 context 'when using a button based provider' do 81 let(:provider) { :github } 82 83 it 'does not lock the user when sign in fails' do 84 request.env['omniauth.error.strategy'] = OmniAuth::Strategies::GitHub.new(nil) 85 86 subject.send(:failure) 87 88 expect(user.reload).not_to be_access_locked 89 end 90 end 91 end 92 93 context 'when sign in fails' do 94 include RoutesHelpers 95 96 let(:extern_uid) { 'my-uid' } 97 let(:provider) { :saml } 98 99 def stub_route_as(path) 100 allow(@routes).to receive(:generate_extras) { [path, []] } 101 end 102 103 it 'calls through to the failure handler' do 104 request.env['omniauth.error'] = OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch") 105 request.env['omniauth.error.strategy'] = OmniAuth::Strategies::SAML.new(nil) 106 stub_route_as('/users/auth/saml/callback') 107 108 ForgeryProtection.with_forgery_protection do 109 post :failure 110 end 111 112 expect(flash[:alert]).to match(/Fingerprint mismatch/) 113 end 114 end 115 116 context 'when a redirect fragment is provided' do 117 let(:provider) { :jwt } 118 let(:extern_uid) { 'my-uid' } 119 120 before do 121 request.env['omniauth.params'] = { 'redirect_fragment' => 'L101' } 122 end 123 124 context 'when a redirect url is stored' do 125 it 'redirects with fragment' do 126 post provider, session: { user_return_to: '/fake/url' } 127 128 expect(response).to redirect_to('/fake/url#L101') 129 end 130 end 131 132 context 'when a redirect url with a fragment is stored' do 133 it 'redirects with the new fragment' do 134 post provider, session: { user_return_to: '/fake/url#replaceme' } 135 136 expect(response).to redirect_to('/fake/url#L101') 137 end 138 end 139 140 context 'when no redirect url is stored' do 141 it 'does not redirect with the fragment' do 142 post provider 143 144 expect(response.redirect?).to be true 145 expect(response.location).not_to include('#L101') 146 end 147 end 148 end 149 150 context 'strategies' do 151 shared_context 'sign_up' do 152 let(:user) { double(email: 'new@example.com') } 153 154 before do 155 stub_omniauth_setting(block_auto_created_users: false) 156 end 157 end 158 159 context 'github' do 160 let(:extern_uid) { 'my-uid' } 161 let(:provider) { :github } 162 163 it_behaves_like 'known sign in' do 164 let(:post_action) { post provider } 165 end 166 167 it 'allows sign in' do 168 post provider 169 170 expect(request.env['warden']).to be_authenticated 171 end 172 173 it 'creates an authentication event record' do 174 expect { post provider }.to change { AuthenticationEvent.count }.by(1) 175 expect(AuthenticationEvent.last.provider).to eq(provider.to_s) 176 end 177 178 context 'when user has no linked provider' do 179 let(:user) { create(:user) } 180 181 before do 182 sign_in user 183 end 184 185 it 'links identity' do 186 expect do 187 post provider 188 user.reload 189 end.to change { user.identities.count }.by(1) 190 end 191 192 context 'and is not allowed to link the provider' do 193 before do 194 allow_any_instance_of(IdentityProviderPolicy).to receive(:can?).with(:link).and_return(false) 195 end 196 197 it 'returns 403' do 198 post provider 199 200 expect(response).to have_gitlab_http_status(:forbidden) 201 end 202 end 203 end 204 205 context 'when user with 2FA is unconfirmed' do 206 render_views 207 208 let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider) } 209 210 before do 211 user.update_column(:confirmed_at, nil) 212 end 213 214 it 'redirects to login page' do 215 post provider 216 217 expect(response).to redirect_to(new_user_session_path) 218 expect(flash[:alert]).to match(/You have to confirm your email address before continuing./) 219 end 220 end 221 222 context 'sign up' do 223 include_context 'sign_up' 224 225 it 'is allowed' do 226 post provider 227 228 expect(request.env['warden']).to be_authenticated 229 end 230 end 231 232 context 'when OAuth is disabled' do 233 before do 234 stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') 235 settings = Gitlab::CurrentSettings.current_application_settings 236 settings.update!(disabled_oauth_sign_in_sources: [provider.to_s]) 237 end 238 239 it 'prevents login via POST' do 240 post provider 241 242 expect(request.env['warden']).not_to be_authenticated 243 end 244 245 it 'shows warning when attempting login' do 246 post provider 247 248 expect(response).to redirect_to new_user_session_path 249 expect(flash[:alert]).to eq('Signing in using GitHub has been disabled') 250 end 251 252 it 'allows linking the disabled provider' do 253 user.identities.destroy_all # rubocop: disable Cop/DestroyAll 254 sign_in(user) 255 256 expect { post provider }.to change { user.reload.identities.count }.by(1) 257 end 258 259 context 'sign up' do 260 include_context 'sign_up' 261 262 it 'is prevented' do 263 post provider 264 265 expect(request.env['warden']).not_to be_authenticated 266 end 267 end 268 end 269 end 270 271 context 'auth0' do 272 let(:extern_uid) { '' } 273 let(:provider) { :auth0 } 274 275 it 'does not allow sign in without extern_uid' do 276 post 'auth0' 277 278 expect(request.env['warden']).not_to be_authenticated 279 expect(response).to have_gitlab_http_status(:found) 280 expect(controller).to set_flash[:alert].to('Wrong extern UID provided. Make sure Auth0 is configured correctly.') 281 end 282 end 283 284 context 'atlassian_oauth2' do 285 let(:provider) { :atlassian_oauth2 } 286 let(:extern_uid) { 'my-uid' } 287 288 context 'when the user and identity already exist' do 289 let(:user) { create(:atlassian_user, extern_uid: extern_uid) } 290 291 it 'allows sign-in' do 292 post :atlassian_oauth2 293 294 expect(request.env['warden']).to be_authenticated 295 end 296 297 it 'sets the username and caller_id in the context' do 298 expect(controller).to receive(:atlassian_oauth2).and_wrap_original do |m, *args| 299 m.call(*args) 300 301 expect(Gitlab::ApplicationContext.current) 302 .to include('meta.user' => user.username, 303 'meta.caller_id' => 'OmniauthCallbacksController#atlassian_oauth2') 304 end 305 306 post :atlassian_oauth2 307 end 308 end 309 310 context 'for a new user' do 311 before do 312 stub_omniauth_setting(enabled: true, auto_link_user: true, allow_single_sign_on: ['atlassian_oauth2']) 313 314 user.destroy! 315 end 316 317 it 'denies sign-in if sign-up is enabled, but block_auto_created_users is set' do 318 post :atlassian_oauth2 319 320 expect(flash[:alert]).to start_with 'Your account is pending approval' 321 end 322 323 it 'accepts sign-in if sign-up is enabled' do 324 stub_omniauth_setting(block_auto_created_users: false) 325 326 post :atlassian_oauth2 327 328 expect(request.env['warden']).to be_authenticated 329 end 330 331 it 'denies sign-in if sign-up is not enabled' do 332 stub_omniauth_setting(allow_single_sign_on: false, block_auto_created_users: false) 333 334 post :atlassian_oauth2 335 336 expect(flash[:alert]).to start_with 'Signing in using your Atlassian account without a pre-existing GitLab account is not allowed.' 337 end 338 end 339 end 340 341 context 'salesforce' do 342 let(:extern_uid) { 'my-uid' } 343 let(:provider) { :salesforce } 344 let(:additional_info) { { extra: { email_verified: false } } } 345 346 context 'without verified email' do 347 it 'does not allow sign in' do 348 post 'salesforce' 349 350 expect(request.env['warden']).not_to be_authenticated 351 expect(response).to have_gitlab_http_status(:found) 352 expect(controller).to set_flash[:alert].to('Email not verified. Please verify your email in Salesforce.') 353 end 354 end 355 356 context 'with verified email' do 357 include_context 'sign_up' 358 let(:additional_info) { { extra: { email_verified: true } } } 359 360 it 'allows sign in' do 361 post 'salesforce' 362 363 expect(request.env['warden']).to be_authenticated 364 end 365 end 366 end 367 end 368 end 369 370 describe '#saml' do 371 let(:last_request_id) { 'ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685' } 372 let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml') } 373 let(:mock_saml_response) { File.read('spec/fixtures/authentication/saml_response.xml') } 374 let(:saml_config) { mock_saml_config_with_upstream_two_factor_authn_contexts } 375 376 def stub_last_request_id(id) 377 session['last_authn_request_id'] = id 378 end 379 380 before do 381 stub_last_request_id(last_request_id) 382 stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], 383 providers: [saml_config]) 384 mock_auth_hash_with_saml_xml('saml', +'my-uid', user.email, mock_saml_response) 385 request.env['devise.mapping'] = Devise.mappings[:user] 386 request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth'] 387 end 388 389 it_behaves_like 'known sign in' do 390 let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') } 391 let(:post_action) { post :saml, params: { SAMLResponse: mock_saml_response } } 392 end 393 394 context 'sign up' do 395 before do 396 user.destroy! 397 end 398 399 it 'denies login if sign up is enabled, but block_auto_created_users is set' do 400 post :saml, params: { SAMLResponse: mock_saml_response } 401 402 expect(flash[:alert]).to start_with 'Your account is pending approval' 403 end 404 405 it 'accepts login if sign up is enabled' do 406 stub_omniauth_setting(block_auto_created_users: false) 407 408 post :saml, params: { SAMLResponse: mock_saml_response } 409 410 expect(request.env['warden']).to be_authenticated 411 end 412 413 it 'denies login if sign up is not enabled' do 414 stub_omniauth_setting(allow_single_sign_on: false, block_auto_created_users: false) 415 416 post :saml, params: { SAMLResponse: mock_saml_response } 417 418 expect(flash[:alert]).to start_with 'Signing in using your saml account without a pre-existing GitLab account is not allowed.' 419 end 420 end 421 422 context 'with GitLab initiated request' do 423 before do 424 post :saml, params: { SAMLResponse: mock_saml_response } 425 end 426 427 context 'when worth two factors' do 428 let(:mock_saml_response) do 429 File.read('spec/fixtures/authentication/saml_response.xml') 430 .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN') 431 end 432 433 it 'expects user to be signed_in' do 434 expect(request.env['warden']).to be_authenticated 435 end 436 end 437 438 context 'when not worth two factors' do 439 it 'expects user to provide second factor' do 440 expect(response).to render_template('devise/sessions/two_factor') 441 expect(request.env['warden']).not_to be_authenticated 442 end 443 end 444 end 445 446 context 'with IdP initiated request' do 447 let(:user) { create(:user) } 448 let(:last_request_id) { '99999' } 449 450 before do 451 sign_in user 452 end 453 454 it 'lets the user know their account isn\'t linked yet' do 455 post :saml, params: { SAMLResponse: mock_saml_response } 456 457 expect(flash[:notice]).to eq 'Request to link SAML account must be authorized' 458 end 459 460 it 'redirects to profile account page' do 461 post :saml, params: { SAMLResponse: mock_saml_response } 462 463 expect(response).to redirect_to(profile_account_path) 464 end 465 466 it 'doesn\'t link a new identity to the user' do 467 expect { post :saml, params: { SAMLResponse: mock_saml_response } }.not_to change { user.identities.count } 468 end 469 470 it 'sets the username and caller_id in the context' do 471 expect(controller).to receive(:saml).and_wrap_original do |m, *args| 472 m.call(*args) 473 474 expect(Gitlab::ApplicationContext.current) 475 .to include('meta.user' => user.username, 476 'meta.caller_id' => 'OmniauthCallbacksController#saml') 477 end 478 479 post :saml, params: { SAMLResponse: mock_saml_response } 480 end 481 end 482 483 context 'with a blocked user trying to log in when there are hooks set up' do 484 let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') } 485 486 subject(:post_action) { post :saml, params: { SAMLResponse: mock_saml_response } } 487 488 before do 489 create(:system_hook) 490 user.block! 491 end 492 493 it { expect { post_action }.not_to raise_error } 494 end 495 end 496 497 describe 'enable admin mode' do 498 include_context 'custom session' 499 500 let(:provider) { :auth0 } 501 let(:extern_uid) { 'my-uid' } 502 let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) } 503 504 def reauthenticate_and_check_admin_mode(expected_admin_mode:) 505 # Initially admin mode disabled 506 expect(subject.current_user_mode.admin_mode?).to be(false) 507 508 # Trigger OmniAuth admin mode flow and expect admin mode status 509 post provider 510 511 expect(request.env['warden']).to be_authenticated 512 expect(subject.current_user_mode.admin_mode?).to be(expected_admin_mode) 513 end 514 515 context 'user and admin mode requested by the same user' do 516 before do 517 sign_in user 518 519 mock_auth_hash(provider.to_s, extern_uid, user.email, additional_info: {}) 520 stub_omniauth_provider(provider, context: request) 521 end 522 523 context 'with a regular user' do 524 it 'cannot be enabled' do 525 reauthenticate_and_check_admin_mode(expected_admin_mode: false) 526 527 expect(response).to redirect_to(root_path) 528 end 529 end 530 531 context 'with an admin user' do 532 let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider, access_level: :admin) } 533 534 context 'when requested first' do 535 before do 536 subject.current_user_mode.request_admin_mode! 537 end 538 539 it 'can be enabled' do 540 reauthenticate_and_check_admin_mode(expected_admin_mode: true) 541 542 expect(response).to redirect_to(admin_root_path) 543 end 544 end 545 546 context 'when not requested first' do 547 it 'cannot be enabled' do 548 reauthenticate_and_check_admin_mode(expected_admin_mode: false) 549 550 expect(response).to redirect_to(root_path) 551 end 552 end 553 end 554 end 555 556 context 'user and admin mode requested by different users' do 557 let(:reauth_extern_uid) { 'another_uid' } 558 let(:reauth_user) { create(:omniauth_user, extern_uid: reauth_extern_uid, provider: provider) } 559 560 before do 561 sign_in user 562 563 mock_auth_hash(provider.to_s, reauth_extern_uid, reauth_user.email, additional_info: {}) 564 stub_omniauth_provider(provider, context: request) 565 end 566 567 context 'with a regular user' do 568 it 'cannot be enabled' do 569 reauthenticate_and_check_admin_mode(expected_admin_mode: false) 570 571 expect(response).to redirect_to(profile_account_path) 572 end 573 end 574 575 context 'with an admin user' do 576 let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider, access_level: :admin) } 577 let(:reauth_user) { create(:omniauth_user, extern_uid: reauth_extern_uid, provider: provider, access_level: :admin) } 578 579 context 'when requested first' do 580 before do 581 subject.current_user_mode.request_admin_mode! 582 end 583 584 it 'cannot be enabled' do 585 reauthenticate_and_check_admin_mode(expected_admin_mode: false) 586 587 expect(response).to redirect_to(new_admin_session_path) 588 end 589 end 590 591 context 'when not requested first' do 592 it 'cannot be enabled' do 593 reauthenticate_and_check_admin_mode(expected_admin_mode: false) 594 595 expect(response).to redirect_to(profile_account_path) 596 end 597 end 598 end 599 end 600 end 601end 602