Commit 30b0f796 authored by Marcos Rocha's avatar Marcos Rocha Committed by Stan Hu

Add endpoint to decide if the ArkoseLabs integration should be triggered

This MR adds an endpoint to verify if the ArkoseLabs should be called

Changelog: added
MR:
parent e7983f2e
# 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