Commit a087a07e authored by Stan Hu's avatar Stan Hu

Merge branch 'mc_rocha-captcha-check' into 'master'

Add endpoint to determine when ArkoseLabs integration should be used

See merge request gitlab-org/gitlab!82751
parents 21e260cb 30b0f796
# frozen_string_literal: true
module Users
class CaptchaChallengeService
attr_reader :user, :request_ip
def initialize(user, request_ip)
@user = user
@request_ip = request_ip
end
def execute
return { result: false } unless Feature.enabled?(:arkose_labs_login_challenge, default_enabled: :yaml)
if never_logged_before? || too_many_login_failures || not_logged_in_past_months || last_login_from_different_ip
return { result: true }
end
{ result: false }
end
private
def never_logged_before?
user.last_sign_in_at.nil?
end
def too_many_login_failures
user.failed_attempts >= 3
end
def not_logged_in_past_months
user.last_sign_in_at <= Date.today - 3.months
end
def last_login_from_different_ip
user.last_sign_in_ip != request_ip
end
end
end
# frozen_string_literal: true
module API
class CaptchaCheck < ::API::Base
feature_category :authentication_and_authorization
params do
requires :username, type: String, desc: 'The username of a user'
end
content_type :json, 'application/json'
default_format :json
resource :users do
desc 'Get captcha check result for ArkoseLabs'
get ':username/captcha_check', requirements: { username: %r{[^/]+} } do
not_found! 'User' unless Feature.enabled?(:arkose_labs_login_challenge, default_enabled: :yaml)
rate_limit_reached = false
check_rate_limit!(:search_rate_limit_unauthenticated, scope: [request.ip]) do
rate_limit_reached = true
end
if rate_limit_reached
present({ result: true }, with: Entities::CaptchaCheck)
else
user = User.find_by_username(params[:username])
not_found! 'User' unless user
present(::Users::CaptchaChallengeService.new(user, request.ip).execute, with: Entities::CaptchaCheck)
end
end
end
end
end
# frozen_string_literal: true
module API
module Entities
class CaptchaCheck < Grape::Entity
expose :result
end
end
end
......@@ -53,6 +53,7 @@ module EE
mount ::API::Iterations
mount ::API::GroupRepositoryStorageMoves
mount ::API::Ci::Minutes
mount ::API::CaptchaCheck
mount ::API::Internal::AppSec::Dast::SiteValidations
mount ::API::Internal::UpcomingReconciliations
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::CaptchaCheck do
let_it_be(:username) { 'TestCaptcha' }
let_it_be_with_reload(:user) { create(:user, username: username) }
describe 'GET users/:username/captcha_check' do
context 'when the feature flag arkose_labs_login_challenge is disabled' do
before do
stub_feature_flags(arkose_labs_login_challenge: false)
end
it 'does return not found status' do
get api("/users/#{username}/captcha_check")
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the feature flag arkose_labs_login_challenge is enabled' do
context 'when the username is invalid' do
let(:invalid_username) { 'invalidUsername' }
it 'does return not found status' do
get api("/users/#{invalid_username}/captcha_check")
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the username has a dot' do
let_it_be(:username) { 'valid.Username' }
let_it_be(:dot_user) { create(:user, username: username) }
it 'does return 200 status' do
get api("/users/#{username}/captcha_check")
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'when the user meets the criteria for the captcha check' do
before do
user.last_sign_in_at = nil
end
it 'does return true' do
get api("/users/#{username}/captcha_check")
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['result']).to be_truthy
end
end
context 'when the user does not meets the criteria for the captcha check' do
before do
user.last_sign_in_at = Date.today - 2.months
user.last_sign_in_ip = '192.168.1.1'
user.save!
end
it 'does return true' do
get api("/users/#{username}/captcha_check"), headers: { 'REMOTE_ADDR' => '192.168.1.1' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['result']).to be_falsey
end
end
context 'when the user reach the rate limit' do
before do
user.last_sign_in_at = Date.today - 2.months
user.last_sign_in_ip = '192.168.1.1'
user.save!
end
it 'does return true' do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)
get api("/users/#{username}/captcha_check"), headers: { 'REMOTE_ADDR' => '192.168.1.1' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['result']).to be_truthy
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::CaptchaChallengeService do
describe '#execute' do
let_it_be_with_reload(:user) { create(:user) }
let_it_be(:request_ip) { '127.0.0.1' }
let(:should_challenge?) { true }
let(:result) { { result: should_challenge? } }
subject { Users::CaptchaChallengeService.new(user, request_ip).execute }
context 'when feature flag arkose_labs_login_challenge is disabled' do
let(:should_challenge?) { false }
before do
stub_feature_flags(arkose_labs_login_challenge: false)
end
it { is_expected.to eq(result) }
end
context 'when feature flag arkose_labs_login_challenge is enabled' do
context 'when the user has never logged in previously' do
before do
user.last_sign_in_at = nil
end
it { is_expected.to eq(result) }
end
context 'when the user has not logged in successfully in more than 3 months' do
before do
user.last_sign_in_at = Date.today - 4.months
end
it { is_expected.to eq(result) }
end
context 'when the user has 3 failed login attempts' do
before do
user.last_sign_in_at = Date.today - 2.months
user.failed_attempts = 3
end
it { is_expected.to eq(result) }
end
context 'when the IP address on this login attempt is different than the last successful login' do
before do
user.last_sign_in_ip = '192.168.1.1'
end
it { is_expected.to eq(result) }
end
context 'when the user has logged in previously in less than 3 months' do
before do
user.last_sign_in_at = Date.today - 2.months
end
context 'when the IP address on this login attempt is the same than the last successful login' do
let(:should_challenge?) { false }
before do
user.last_sign_in_ip = request_ip
end
it { is_expected.to eq(result) }
end
context 'when The IP address on this login attempt is different than the last successful login' do
before do
user.last_sign_in_ip = '192.168.1.1'
end
it { is_expected.to eq(result) }
end
end
end
end
end
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