Commit d1a115bb authored by Stan Hu's avatar Stan Hu

Show a reCAPTCHA on signin page if custom header is set

This will only be displayed if `X-GitLab-Show-Login-Captcha` is set as an HTTP
header.
parent 15f91b86
...@@ -3,6 +3,7 @@ class SessionsController < Devise::SessionsController ...@@ -3,6 +3,7 @@ class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable include Devise::Controllers::Rememberable
include Recaptcha::ClientHelper include Recaptcha::ClientHelper
include Recaptcha::Verify
prepend EE::SessionsController prepend EE::SessionsController
skip_before_action :check_two_factor_requirement, only: [:destroy] skip_before_action :check_two_factor_requirement, only: [:destroy]
...@@ -10,15 +11,20 @@ class SessionsController < Devise::SessionsController ...@@ -10,15 +11,20 @@ class SessionsController < Devise::SessionsController
prepend_before_action :check_initial_setup, only: [:new] prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor, prepend_before_action :authenticate_with_two_factor,
if: :two_factor_enabled?, only: [:create] if: :two_factor_enabled?, only: [:create]
prepend_before_action :check_captcha, only: [:create]
prepend_before_action :store_redirect_uri, only: [:new] prepend_before_action :store_redirect_uri, only: [:new]
prepend_before_action :ldap_servers, only: [:new, :create]
before_action :auto_sign_in_with_provider, only: [:new] before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha before_action :load_recaptcha
after_action :log_failed_login, only: [:new], if: :failed_login? after_action :log_failed_login, only: [:new], if: :failed_login?
helper_method :captcha_enabled?
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
def new def new
set_minimum_password_length set_minimum_password_length
@ldap_servers = Gitlab::Auth::LDAP::Config.available_servers
super super
end end
...@@ -47,6 +53,25 @@ class SessionsController < Devise::SessionsController ...@@ -47,6 +53,25 @@ class SessionsController < Devise::SessionsController
private private
def captcha_enabled?
request.headers[CAPTCHA_HEADER] && Gitlab::Recaptcha.enabled?
end
# From https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise#devisepasswordscontroller
def check_captcha
return unless user_params[:password].present?
return unless captcha_enabled?
return unless Gitlab::Recaptcha.load_configurations!
unless verify_recaptcha
self.resource = resource_class.new
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
flash.delete :recaptcha_error
respond_with_navigational(resource) { render :new }
end
end
def log_failed_login def log_failed_login
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}") Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
end end
...@@ -153,6 +178,10 @@ class SessionsController < Devise::SessionsController ...@@ -153,6 +178,10 @@ class SessionsController < Devise::SessionsController
Gitlab::Recaptcha.load_configurations! Gitlab::Recaptcha.load_configurations!
end end
def ldap_servers
@ldap_servers ||= Gitlab::Auth::LDAP::Config.available_servers
end
def authentication_method def authentication_method
if user_params[:otp_attempt] if user_params[:otp_attempt]
"two-factor" "two-factor"
......
...@@ -12,5 +12,9 @@ ...@@ -12,5 +12,9 @@
%span Remember me %span Remember me
.float-right.forgot-password .float-right.forgot-password
= link_to "Forgot your password?", new_password_path(:user) = link_to "Forgot your password?", new_password_path(:user)
%div
- if captcha_enabled?
= recaptcha_tags
.submit-container.move-submit-down .submit-container.move-submit-down
= f.submit "Sign in", class: "btn btn-save" = f.submit "Sign in", class: "btn btn-save"
...@@ -53,21 +53,22 @@ describe SessionsController do ...@@ -53,21 +53,22 @@ describe SessionsController do
include UserActivitiesHelpers include UserActivitiesHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user_params) { { login: user.username, password: user.password } }
it 'authenticates user correctly' do it 'authenticates user correctly' do
post(:create, user: { login: user.username, password: user.password }) post(:create, user: user_params)
expect(subject.current_user). to eq user expect(subject.current_user). to eq user
end end
it 'creates an audit log record' do it 'creates an audit log record' do
expect { post(:create, user: { login: user.username, password: user.password }) }.to change { SecurityEvent.count }.by(1) expect { post(:create, user: user_params) }.to change { SecurityEvent.count }.by(1)
expect(SecurityEvent.last.details[:with]).to eq('standard') expect(SecurityEvent.last.details[:with]).to eq('standard')
end end
include_examples 'user login request with unique ip limit', 302 do include_examples 'user login request with unique ip limit', 302 do
def request def request
post(:create, user: { login: user.username, password: user.password }) post(:create, user: user_params)
expect(subject.current_user).to eq user expect(subject.current_user).to eq user
subject.sign_out user subject.sign_out user
end end
...@@ -75,10 +76,40 @@ describe SessionsController do ...@@ -75,10 +76,40 @@ describe SessionsController do
it 'updates the user activity' do it 'updates the user activity' do
expect do expect do
post(:create, user: { login: user.username, password: user.password }) post(:create, user: user_params)
end.to change { user_activity(user) } end.to change { user_activity(user) }
end end
end end
context 'when reCAPTCHA is enabled' do
let(:user) { create(:user) }
let(:user_params) { { login: user.username, password: user.password } }
before do
stub_application_setting(recaptcha_enabled: true)
request.headers[described_class::CAPTCHA_HEADER] = 1
end
it 'displays an error when the reCAPTCHA is not solved' do
# Without this, `verify_recaptcha` arbitraily returns true in test env
Recaptcha.configuration.skip_verify_env.delete('test')
post(:create, user: user_params)
expect(response).to render_template(:new)
expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
expect(subject.current_user).to be_nil
end
it 'successfully logs in a user when reCAPTCHA is solved' do
# Avoid test ordering issue and ensure `verify_recaptcha` returns true
Recaptcha.configuration.skip_verify_env << 'test'
post(:create, user: user_params)
expect(subject.current_user).to eq user
end
end
end end
context 'when using two-factor authentication via OTP' do context 'when using two-factor authentication via OTP' do
......
...@@ -6,6 +6,7 @@ describe 'devise/shared/_signin_box' do ...@@ -6,6 +6,7 @@ describe 'devise/shared/_signin_box' do
stub_devise stub_devise
assign(:ldap_servers, []) assign(:ldap_servers, [])
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings) allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
allow(view).to receive(:captcha_enabled?).and_return(false)
end end
it 'is shown when Crowd is enabled' do it 'is shown when Crowd is enabled' 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