Commit b205b9d9 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-490-2fa-bypass-using-git-command' into 'master'

Security: 2FA bypass using git command

See merge request gitlab-org/security/gitlab!1739
parents 8f646b53 79c523c3
......@@ -172,7 +172,11 @@ module Gitlab
user = find_with_user_password(login, password)
return unless user
raise Gitlab::Auth::MissingPersonalAccessTokenError if user.two_factor_enabled?
verifier = TwoFactorAuthVerifier.new(user)
if user.two_factor_enabled? || verifier.two_factor_authentication_enforced?
raise Gitlab::Auth::MissingPersonalAccessTokenError
end
Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
end
......
......@@ -9,6 +9,10 @@ module Gitlab
@current_user = current_user
end
def two_factor_authentication_enforced?
two_factor_authentication_required? && two_factor_grace_period_expired?
end
def two_factor_authentication_required?
Gitlab::CurrentSettings.require_two_factor_authentication? ||
current_user&.require_two_factor_authentication_from_group?
......
......@@ -3,33 +3,50 @@
require 'spec_helper'
RSpec.describe Gitlab::Auth::TwoFactorAuthVerifier do
let(:user) { create(:user) }
using RSpec::Parameterized::TableSyntax
subject { described_class.new(user) }
subject(:verifier) { described_class.new(user) }
describe '#two_factor_authentication_required?' do
describe 'when it is required on application level' do
it 'returns true' do
stub_application_setting require_two_factor_authentication: true
let(:user) { build_stubbed(:user, otp_grace_period_started_at: Time.zone.now) }
expect(subject.two_factor_authentication_required?).to be_truthy
end
end
describe '#two_factor_authentication_enforced?' do
subject { verifier.two_factor_authentication_enforced? }
describe 'when it is required on group level' do
it 'returns true' do
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true)
where(:instance_level_enabled, :group_level_enabled, :grace_period_expired, :should_be_enforced) do
false | false | true | false
true | false | false | false
true | false | true | true
false | true | false | false
false | true | true | true
end
expect(subject.two_factor_authentication_required?).to be_truthy
with_them do
before do
stub_application_setting(require_two_factor_authentication: instance_level_enabled)
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(group_level_enabled)
stub_application_setting(two_factor_grace_period: grace_period_expired ? 0 : 1.month.in_hours)
end
it { is_expected.to eq(should_be_enforced) }
end
end
describe 'when it is not required' do
it 'returns false when not required on group level' do
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false)
describe '#two_factor_authentication_required?' do
subject { verifier.two_factor_authentication_required? }
where(:instance_level_enabled, :group_level_enabled, :should_be_required) do
true | false | true
false | true | true
false | false | false
end
expect(subject.two_factor_authentication_required?).to be_falsey
with_them do
before do
stub_application_setting(require_two_factor_authentication: instance_level_enabled)
allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(group_level_enabled)
end
it { is_expected.to eq(should_be_required) }
end
end
......@@ -85,25 +102,21 @@ RSpec.describe Gitlab::Auth::TwoFactorAuthVerifier do
end
describe '#two_factor_grace_period_expired?' do
before do
allow(user).to receive(:otp_grace_period_started_at).and_return(4.hours.ago)
end
it 'returns true if the grace period has expired' do
allow(subject).to receive(:two_factor_grace_period).and_return(2)
stub_application_setting two_factor_grace_period: 0
expect(subject.two_factor_grace_period_expired?).to be_truthy
end
it 'returns false if the grace period has not expired' do
allow(subject).to receive(:two_factor_grace_period).and_return(6)
stub_application_setting two_factor_grace_period: 1.month.in_hours
expect(subject.two_factor_grace_period_expired?).to be_falsey
end
context 'when otp_grace_period_started_at is nil' do
it 'returns false' do
allow(user).to receive(:otp_grace_period_started_at).and_return(nil)
user.otp_grace_period_started_at = nil
expect(subject.two_factor_grace_period_expired?).to be_falsey
end
......
......@@ -386,7 +386,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
shared_examples 'with an invalid access token' do
it 'fails for a non-member' do
expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, ip: 'ip'))
.to have_attributes(auth_failure )
.to have_attributes(auth_failure)
end
context 'when project bot user is blocked' do
......@@ -396,7 +396,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'fails for a blocked project bot' do
expect(gl_auth.find_for_git_client(project_bot_user.username, access_token.token, project: project, ip: 'ip'))
.to have_attributes(auth_failure )
.to have_attributes(auth_failure)
end
end
end
......@@ -466,6 +466,41 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
.to have_attributes(auth_failure)
end
context 'when 2fa is enabled globally' do
let_it_be(:user) do
create(:user, username: 'normal_user', password: 'my-secret', otp_grace_period_started_at: 1.day.ago)
end
before do
stub_application_setting(require_two_factor_authentication: true)
end
it 'fails if grace period expired' do
stub_application_setting(two_factor_grace_period: 0)
expect { gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') }
.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
end
it 'goes through if grace period is not expired yet' do
stub_application_setting(two_factor_grace_period: 72)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
.to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
end
end
context 'when 2fa is enabled personally' do
let(:user) do
create(:user, :two_factor, username: 'normal_user', password: 'my-secret', otp_grace_period_started_at: 1.day.ago)
end
it 'fails' do
expect { gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip') }
.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
end
end
it 'goes through lfs authentication' do
user = create(
:user,
......@@ -757,16 +792,16 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
describe 'find_with_user_password' do
let!(:user) do
create(:user,
username: username,
password: password,
password_confirmation: password)
username: username,
password: password,
password_confirmation: password)
end
let(:username) { 'John' } # username isn't lowercase, test this
let(:password) { 'my-secret' }
it "finds user by valid login/password" do
expect( gl_auth.find_with_user_password(username, password) ).to eql user
expect(gl_auth.find_with_user_password(username, password)).to eql user
end
it 'finds user by valid email/password with case-insensitive email' do
......@@ -779,12 +814,12 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it "does not find user with invalid password" do
password = 'wrong'
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
expect(gl_auth.find_with_user_password(username, password)).not_to eql user
end
it "does not find user with invalid login" do
user = 'wrong'
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
expect(gl_auth.find_with_user_password(username, password)).not_to eql user
end
include_examples 'user login operation with unique ip limit' do
......@@ -796,13 +831,13 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'finds the user in deactivated state' do
user.deactivate!
expect( gl_auth.find_with_user_password(username, password) ).to eql user
expect(gl_auth.find_with_user_password(username, password)).to eql user
end
it "does not find user in blocked state" do
user.block
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
expect(gl_auth.find_with_user_password(username, password)).not_to eql user
end
it 'does not find user in locked state' do
......@@ -814,13 +849,13 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it "does not find user in ldap_blocked state" do
user.ldap_block
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
expect(gl_auth.find_with_user_password(username, password)).not_to eql user
end
it 'does not find user in blocked_pending_approval state' do
user.block_pending_approval
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
expect(gl_auth.find_with_user_password(username, password)).not_to eql user
end
context 'with increment_failed_attempts' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment