694 lines
21 KiB
Ruby
694 lines
21 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe OmniauthCallbacksController, type: :controller, feature_category: :system_access do
|
|
include LoginHelpers
|
|
|
|
describe 'omniauth' do
|
|
let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) }
|
|
let(:additional_info) { {} }
|
|
|
|
before do
|
|
@original_env_config_omniauth_auth = mock_auth_hash(provider.to_s, extern_uid, user.email, additional_info: additional_info)
|
|
stub_omniauth_provider(provider, context: request)
|
|
end
|
|
|
|
after do
|
|
Rails.application.env_config['omniauth.auth'] = @original_env_config_omniauth_auth
|
|
end
|
|
|
|
context 'a deactivated user' do
|
|
let(:provider) { :github }
|
|
let(:extern_uid) { 'my-uid' }
|
|
|
|
before do
|
|
user.deactivate!
|
|
post provider
|
|
end
|
|
|
|
it 'allows sign in' do
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
|
|
it 'activates the user' do
|
|
expect(user.reload.active?).to be_truthy
|
|
end
|
|
|
|
it 'shows reactivation flash message after logging in' do
|
|
expect(flash[:notice]).to eq('Welcome back! Your account had been deactivated due to inactivity but is now reactivated.')
|
|
end
|
|
end
|
|
|
|
context 'when sign in is not valid' do
|
|
let(:provider) { :github }
|
|
let(:extern_uid) { 'my-uid' }
|
|
|
|
it 'renders omniauth error page' do
|
|
allow_next_instance_of(Gitlab::Auth::OAuth::User) do |instance|
|
|
allow(instance).to receive(:valid_sign_in?).and_return(false)
|
|
end
|
|
|
|
post provider
|
|
|
|
expect(response).to render_template("errors/omniauth_error")
|
|
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
|
end
|
|
end
|
|
|
|
context 'when the user is on the last sign in attempt' do
|
|
let(:extern_uid) { 'my-uid' }
|
|
|
|
before do
|
|
user.update!(failed_attempts: User.maximum_attempts.pred)
|
|
subject.response = ActionDispatch::Response.new
|
|
end
|
|
|
|
context 'when using a form based provider' do
|
|
let(:provider) { :ldap }
|
|
|
|
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)
|
|
|
|
subject.send(:failure)
|
|
|
|
expect(user.reload).to be_access_locked
|
|
end
|
|
end
|
|
|
|
context 'when using a button based provider' do
|
|
let(:provider) { :github }
|
|
|
|
it 'does not lock the user when sign in fails' do
|
|
request.env['omniauth.error.strategy'] = OmniAuth::Strategies::GitHub.new(nil)
|
|
|
|
subject.send(:failure)
|
|
|
|
expect(user.reload).not_to be_access_locked
|
|
end
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
it 'calls through to the failure handler' do
|
|
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
|
|
|
|
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
|
|
post provider, session: { user_return_to: '/fake/url' }
|
|
|
|
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
|
|
post provider, session: { user_return_to: '/fake/url#replaceme' }
|
|
|
|
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
|
|
|
|
context 'strategies' do
|
|
shared_context 'sign_up' do
|
|
let(:user) { double(email: 'new@example.com') }
|
|
|
|
before do
|
|
stub_omniauth_setting(block_auto_created_users: false)
|
|
end
|
|
end
|
|
|
|
context 'github' do
|
|
let(:extern_uid) { 'my-uid' }
|
|
let(:provider) { :github }
|
|
|
|
it_behaves_like 'known sign in' do
|
|
let(:post_action) { post provider }
|
|
end
|
|
|
|
it 'allows sign in' do
|
|
post provider
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
|
|
it 'creates an authentication event record' do
|
|
expect { post provider }.to change { AuthenticationEvent.count }.by(1)
|
|
expect(AuthenticationEvent.last.provider).to eq(provider.to_s)
|
|
end
|
|
|
|
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(:forbidden)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when a user has 2FA enabled' do
|
|
render_views
|
|
|
|
let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider) }
|
|
|
|
context 'when a user is unconfirmed' do
|
|
before do
|
|
stub_application_setting_enum('email_confirmation_setting', 'hard')
|
|
|
|
user.update!(confirmed_at: nil)
|
|
end
|
|
|
|
it 'redirects to login page' do
|
|
post provider
|
|
|
|
expect(response).to redirect_to(new_user_session_path)
|
|
expect(flash[:alert]).to match(/You have to confirm your email address before continuing./)
|
|
end
|
|
end
|
|
|
|
context 'when a user is confirmed' do
|
|
it 'returns 200 response' do
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'sign up' do
|
|
include_context 'sign_up'
|
|
|
|
context 'when intent to register is added to omniauth params' do
|
|
before do
|
|
request.env['omniauth.params'] = { 'intent' => 'register' }
|
|
end
|
|
|
|
it 'is allowed' do
|
|
post provider
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
|
|
it 'redirects to welcome path' do
|
|
post provider
|
|
|
|
expect(response).to redirect_to(users_sign_up_welcome_path)
|
|
end
|
|
end
|
|
|
|
context 'when intent to register is not added to omniauth params' do
|
|
it 'is allowed' do
|
|
post provider
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
|
|
it 'redirects to root path' do
|
|
post provider
|
|
|
|
expect(response).to redirect_to(root_path)
|
|
end
|
|
end
|
|
end
|
|
|
|
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
|
|
|
|
it 'prevents login via POST' do
|
|
post provider
|
|
|
|
expect(request.env['warden']).not_to be_authenticated
|
|
end
|
|
|
|
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
|
|
user.identities.destroy_all # rubocop: disable Cop/DestroyAll
|
|
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).to have_gitlab_http_status(:found)
|
|
expect(controller).to set_flash[:alert].to('Wrong extern UID provided. Make sure Auth0 is configured correctly.')
|
|
end
|
|
end
|
|
|
|
context 'atlassian_oauth2' do
|
|
let(:provider) { :atlassian_oauth2 }
|
|
let(:extern_uid) { 'my-uid' }
|
|
|
|
context 'when the user and identity already exist' do
|
|
let(:user) { create(:atlassian_user, extern_uid: extern_uid) }
|
|
|
|
it 'allows sign-in' do
|
|
post :atlassian_oauth2
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
|
|
it 'sets the username and caller_id in the context' do
|
|
expect(controller).to receive(:atlassian_oauth2).and_wrap_original do |m, *args|
|
|
m.call(*args)
|
|
|
|
expect(Gitlab::ApplicationContext.current).to include(
|
|
'meta.user' => user.username,
|
|
'meta.caller_id' => 'OmniauthCallbacksController#atlassian_oauth2'
|
|
)
|
|
end
|
|
|
|
post :atlassian_oauth2
|
|
end
|
|
end
|
|
|
|
context 'for a new user' do
|
|
before do
|
|
stub_omniauth_setting(enabled: true, auto_link_user: true, allow_single_sign_on: ['atlassian_oauth2'])
|
|
|
|
user.destroy!
|
|
end
|
|
|
|
it 'denies sign-in if sign-up is enabled, but block_auto_created_users is set' do
|
|
post :atlassian_oauth2
|
|
|
|
expect(flash[:alert]).to start_with 'Your account is pending approval'
|
|
end
|
|
|
|
it 'accepts sign-in if sign-up is enabled' do
|
|
stub_omniauth_setting(block_auto_created_users: false)
|
|
|
|
post :atlassian_oauth2
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
|
|
it 'denies sign-in if sign-up is not enabled' do
|
|
stub_omniauth_setting(allow_single_sign_on: false, block_auto_created_users: false)
|
|
|
|
post :atlassian_oauth2
|
|
|
|
expect(flash[:alert]).to start_with 'Signing in using your Atlassian account without a pre-existing GitLab account is not allowed.'
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'salesforce' do
|
|
let(:extern_uid) { 'my-uid' }
|
|
let(:provider) { :salesforce }
|
|
let(:additional_info) { { extra: { email_verified: false } } }
|
|
|
|
context 'without verified email' do
|
|
it 'does not allow sign in' do
|
|
post 'salesforce'
|
|
|
|
expect(request.env['warden']).not_to be_authenticated
|
|
expect(response).to have_gitlab_http_status(:found)
|
|
expect(controller).to set_flash[:alert].to('Email not verified. Please verify your email in Salesforce.')
|
|
end
|
|
end
|
|
|
|
context 'with verified email' do
|
|
include_context 'sign_up'
|
|
let(:additional_info) { { extra: { email_verified: true } } }
|
|
|
|
it 'allows sign in' do
|
|
post 'salesforce'
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with snowplow tracking', :snowplow do
|
|
let(:provider) { 'google_oauth2' }
|
|
let(:extern_uid) { 'my-uid' }
|
|
|
|
context 'when sign_in' do
|
|
it 'does not track the event' do
|
|
post provider
|
|
expect_no_snowplow_event
|
|
end
|
|
end
|
|
|
|
context 'when sign_up' do
|
|
let(:user) { double(email: generate(:email)) }
|
|
|
|
it 'tracks the event' do
|
|
post provider
|
|
|
|
expect_snowplow_event(
|
|
category: described_class.name,
|
|
action: "#{provider}_sso",
|
|
user: User.find_by(email: user.email)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#openid_connect' do
|
|
let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) }
|
|
let(:extern_uid) { 'my-uid' }
|
|
let(:provider) { 'openid_connect' }
|
|
|
|
before do
|
|
prepare_provider_route('openid_connect')
|
|
|
|
mock_auth_hash(provider, extern_uid, user.email, additional_info: {})
|
|
|
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
|
request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth']
|
|
end
|
|
|
|
it_behaves_like 'known sign in' do
|
|
let(:post_action) { post provider }
|
|
end
|
|
|
|
it 'allows sign in' do
|
|
post provider
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
end
|
|
|
|
describe '#saml' do
|
|
let(:last_request_id) { 'ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685' }
|
|
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 }
|
|
|
|
def stub_last_request_id(id)
|
|
session['last_authn_request_id'] = id
|
|
end
|
|
|
|
before do
|
|
stub_last_request_id(last_request_id)
|
|
stub_omniauth_saml_config(
|
|
enabled: true,
|
|
auto_link_saml_user: true,
|
|
allow_single_sign_on: ['saml'],
|
|
providers: [saml_config]
|
|
)
|
|
mock_auth_hash_with_saml_xml('saml', +'my-uid', user.email, mock_saml_response)
|
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
|
request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth']
|
|
end
|
|
|
|
it_behaves_like 'known sign in' do
|
|
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') }
|
|
let(:post_action) { post :saml, params: { SAMLResponse: mock_saml_response } }
|
|
end
|
|
|
|
context 'sign up' do
|
|
before do
|
|
user.destroy!
|
|
end
|
|
|
|
it 'denies login if sign up is enabled, but block_auto_created_users is set' do
|
|
post :saml, params: { SAMLResponse: mock_saml_response }
|
|
|
|
expect(flash[:alert]).to start_with 'Your account is pending approval'
|
|
end
|
|
|
|
it 'accepts login if sign up is enabled' do
|
|
stub_omniauth_setting(block_auto_created_users: false)
|
|
|
|
post :saml, params: { SAMLResponse: mock_saml_response }
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
|
|
it 'denies login if sign up is not enabled' do
|
|
stub_omniauth_setting(allow_single_sign_on: false, block_auto_created_users: false)
|
|
|
|
post :saml, params: { SAMLResponse: mock_saml_response }
|
|
|
|
expect(flash[:alert]).to start_with 'Signing in using your saml account without a pre-existing GitLab account is not allowed.'
|
|
end
|
|
end
|
|
|
|
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')
|
|
.gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN')
|
|
end
|
|
|
|
it 'expects user to be signed_in' do
|
|
expect(request.env['warden']).to be_authenticated
|
|
end
|
|
end
|
|
|
|
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
|
|
end
|
|
end
|
|
|
|
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 }
|
|
end
|
|
|
|
it 'sets the username and caller_id in the context' do
|
|
expect(controller).to receive(:saml).and_wrap_original do |m, *args|
|
|
m.call(*args)
|
|
|
|
expect(Gitlab::ApplicationContext.current).to include(
|
|
'meta.user' => user.username,
|
|
'meta.caller_id' => 'OmniauthCallbacksController#saml'
|
|
)
|
|
end
|
|
|
|
post :saml, params: { SAMLResponse: mock_saml_response }
|
|
end
|
|
end
|
|
|
|
context 'with a blocked user trying to log in when there are hooks set up' do
|
|
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') }
|
|
|
|
subject(:post_action) { post :saml, params: { SAMLResponse: mock_saml_response } }
|
|
|
|
before do
|
|
create(:system_hook)
|
|
user.block!
|
|
end
|
|
|
|
it { expect { post_action }.not_to raise_error }
|
|
end
|
|
end
|
|
|
|
describe 'enable admin mode' do
|
|
include_context 'custom session'
|
|
|
|
let(:provider) { :auth0 }
|
|
let(:extern_uid) { 'my-uid' }
|
|
let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) }
|
|
|
|
def reauthenticate_and_check_admin_mode(expected_admin_mode:)
|
|
# Initially admin mode disabled
|
|
expect(subject.current_user_mode.admin_mode?).to be(false)
|
|
|
|
# Trigger OmniAuth admin mode flow and expect admin mode status
|
|
post provider
|
|
|
|
expect(request.env['warden']).to be_authenticated
|
|
expect(subject.current_user_mode.admin_mode?).to be(expected_admin_mode)
|
|
end
|
|
|
|
context 'user and admin mode requested by the same user' do
|
|
before do
|
|
sign_in user
|
|
|
|
mock_auth_hash(provider.to_s, extern_uid, user.email, additional_info: {})
|
|
stub_omniauth_provider(provider, context: request)
|
|
end
|
|
|
|
context 'with a regular user' do
|
|
it 'cannot be enabled' do
|
|
reauthenticate_and_check_admin_mode(expected_admin_mode: false)
|
|
|
|
expect(response).to redirect_to(root_path)
|
|
end
|
|
end
|
|
|
|
context 'with an admin user' do
|
|
let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider, access_level: :admin) }
|
|
|
|
context 'when requested first' do
|
|
before do
|
|
subject.current_user_mode.request_admin_mode!
|
|
end
|
|
|
|
it 'can be enabled' do
|
|
reauthenticate_and_check_admin_mode(expected_admin_mode: true)
|
|
|
|
expect(response).to redirect_to(admin_root_path)
|
|
end
|
|
end
|
|
|
|
context 'when not requested first' do
|
|
it 'cannot be enabled' do
|
|
reauthenticate_and_check_admin_mode(expected_admin_mode: false)
|
|
|
|
expect(response).to redirect_to(root_path)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'user and admin mode requested by different users' do
|
|
let(:reauth_extern_uid) { 'another_uid' }
|
|
let(:reauth_user) { create(:omniauth_user, extern_uid: reauth_extern_uid, provider: provider) }
|
|
|
|
before do
|
|
sign_in user
|
|
|
|
mock_auth_hash(provider.to_s, reauth_extern_uid, reauth_user.email, additional_info: {})
|
|
stub_omniauth_provider(provider, context: request)
|
|
end
|
|
|
|
context 'with a regular user' do
|
|
it 'cannot be enabled' do
|
|
reauthenticate_and_check_admin_mode(expected_admin_mode: false)
|
|
|
|
expect(response).to redirect_to(profile_account_path)
|
|
end
|
|
end
|
|
|
|
context 'with an admin user' do
|
|
let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider, access_level: :admin) }
|
|
let(:reauth_user) { create(:omniauth_user, extern_uid: reauth_extern_uid, provider: provider, access_level: :admin) }
|
|
|
|
context 'when requested first' do
|
|
before do
|
|
subject.current_user_mode.request_admin_mode!
|
|
end
|
|
|
|
it 'cannot be enabled' do
|
|
reauthenticate_and_check_admin_mode(expected_admin_mode: false)
|
|
|
|
expect(response).to redirect_to(new_admin_session_path)
|
|
end
|
|
end
|
|
|
|
context 'when not requested first' do
|
|
it 'cannot be enabled' do
|
|
reauthenticate_and_check_admin_mode(expected_admin_mode: false)
|
|
|
|
expect(response).to redirect_to(profile_account_path)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|