Commit 9b48d9f4 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 6d2a48d5
......@@ -3,21 +3,27 @@ class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
include Recaptcha::ClientHelper
include Recaptcha::Verify
skip_before_action :check_two_factor_requirement, only: [:destroy]
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor,
if: :two_factor_enabled?, only: [:create]
prepend_before_action :check_captcha, only: [:create]
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 :load_recaptcha
after_action :log_failed_login, only: [:new], if: :failed_login?
helper_method :captcha_enabled?
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
def new
set_minimum_password_length
@ldap_servers = Gitlab::Auth::LDAP::Config.available_servers
super
end
......@@ -46,6 +52,25 @@ class SessionsController < Devise::SessionsController
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
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
end
......@@ -152,6 +177,10 @@ class SessionsController < Devise::SessionsController
Gitlab::Recaptcha.load_configurations!
end
def ldap_servers
@ldap_servers ||= Gitlab::Auth::LDAP::Config.available_servers
end
def authentication_method
if user_params[:otp_attempt]
"two-factor"
......
......@@ -12,5 +12,9 @@
%span Remember me
.float-right.forgot-password
= link_to "Forgot your password?", new_password_path(:user)
%div
- if captcha_enabled?
= recaptcha_tags
.submit-container.move-submit-down
= f.submit "Sign in", class: "btn btn-save"
......@@ -20,4 +20,21 @@ To use reCAPTCHA, first you must create a site and private key.
6. Check the `Enable reCAPTCHA` checkbox
7. Save the configuration.
7. Save the configuration.
## Enabling reCAPTCHA for user logins via passwords
By default, reCAPTCHA is only enabled for user registrations. To enable it for
user logins via passwords, the `X-GitLab-Show-Login-Captcha` HTTP header must
be set. For example, in NGINX, this can be done via the `proxy_set_header`
configuration variable:
```
proxy_set_header X-GitLab-Show-Login-Captcha 1;
```
In GitLab Omnibus, this can be configured via `/etc/gitlab/gitlab.rb`:
```ruby
nginx['proxy_set_headers'] = { 'X-GitLab-Show-Login-Captcha' => 1 }
```
......@@ -53,21 +53,22 @@ describe SessionsController do
include UserActivitiesHelpers
let(:user) { create(:user) }
let(:user_params) { { login: user.username, password: user.password } }
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
end
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')
end
include_examples 'user login request with unique ip limit', 302 do
def request
post(:create, user: { login: user.username, password: user.password })
post(:create, user: user_params)
expect(subject.current_user).to eq user
subject.sign_out user
end
......@@ -75,10 +76,40 @@ describe SessionsController do
it 'updates the user activity' do
expect do
post(:create, user: { login: user.username, password: user.password })
post(:create, user: user_params)
end.to change { user_activity(user) }
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
context 'when using two-factor authentication via OTP' do
......
......@@ -6,6 +6,7 @@ describe 'devise/shared/_signin_box' do
stub_devise
assign(:ldap_servers, [])
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
allow(view).to receive(:captcha_enabled?).and_return(false)
end
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