Commit 89341262 authored by Stan Hu's avatar Stan Hu

Merge branch 'mc_rocha-add-arkose-data-to-custom-attributes' into 'master'

Add arkose verify response details to custom attributes

See merge request gitlab-org/gitlab!84394
parents 679fad53 5afeadbb
......@@ -5,4 +5,14 @@ class UserCustomAttribute < ApplicationRecord
validates :user_id, :key, :value, presence: true
validates :key, uniqueness: { scope: [:user_id] }
def self.upsert_custom_attributes(custom_attributes)
created_at = Date.today
updated_at = Date.today
custom_attributes.map! do |custom_attribute|
custom_attribute.merge({ created_at: created_at, updated_at: updated_at })
end
upsert_all(custom_attributes, unique_by: [:user_id, :key])
end
end
......@@ -96,7 +96,7 @@ module EE
return unless user.present?
if Arkose::UserVerificationService.new(session_token: params[:arkose_labs_token], userid: user.id).execute
if Arkose::UserVerificationService.new(session_token: params[:arkose_labs_token], user: user).execute
increment_successful_login_captcha_counter
else
increment_failed_login_captcha_counter
......
# frozen_string_literal: true
module Arkose
class UserVerificationService
attr_reader :url, :session_token, :userid
attr_reader :url, :session_token, :user
VERIFY_URL = 'http://verify-api.arkoselabs.com/api/v4/verify'
ALLOWLIST_TELLTALE = 'gitlab1-whitelist-qa-team'
def initialize(session_token:, userid:)
def initialize(session_token:, user:)
@session_token = session_token
@userid = userid
@user = user
end
def execute
response = Gitlab::HTTP.perform_request(Net::HTTP::Post, VERIFY_URL, body: body)
logger.info(build_message("Arkose verify response: #{response.parsed_response}"))
response = Gitlab::HTTP.perform_request(Net::HTTP::Post, VERIFY_URL, body: body).parsed_response
logger.info(build_message("Arkose verify response: #{response}"))
return false if invalid_token(response)
add_or_update_arkose_attributes(response)
allowlisted?(response) || (challenge_solved?(response) && low_risk?(response))
rescue StandardError => error
payload = { session_token: session_token, log_data: userid }
payload = { session_token: session_token, log_data: user.id }
Gitlab::ExceptionLogFormatter.format!(error, payload)
Gitlab::ErrorTracking.track_exception(error)
logger.error(build_message("Error verifying user on Arkose: #{payload}"))
......@@ -29,11 +31,46 @@ module Arkose
private
def add_or_update_arkose_attributes(response)
return if Gitlab::Database.read_only?
custom_attributes = custom_attributes(response)
UserCustomAttribute.upsert_custom_attributes(custom_attributes)
end
def custom_attributes(response)
custom_attributes = []
custom_attributes.push({ key: 'arkose_session', value: session_id(response) })
custom_attributes.push({ key: 'arkose_risk_band', value: risk_band(response) })
custom_attributes.push({ key: 'arkose_global_score', value: global_score(response) })
custom_attributes.push({ key: 'arkose_custom_score', value: custom_score(response) })
custom_attributes.map! { |custom_attribute| custom_attribute.merge({ user_id: user.id }) }
custom_attributes
end
def custom_score(response)
response&.dig('session_risk', 'custom', 'score') || 0
end
def global_score(response)
response&.dig('session_risk', 'global', 'score') || 0
end
def risk_band(response)
response&.dig('session_risk', 'risk_band') || 'Unavailable'
end
def session_id(response)
response&.dig('session_details', 'session') || 'Unavailable'
end
def body
{
private_key: Settings.arkose['private_key'],
session_token: session_token,
log_data: userid
log_data: user.id
}
end
......@@ -46,21 +83,21 @@ module Arkose
end
def invalid_token(response)
response.parsed_response&.key?('error')
response&.key?('error')
end
def challenge_solved?(response)
solved = response.parsed_response&.dig('session_details', 'solved')
solved = response&.dig('session_details', 'solved')
solved.nil? ? true : solved
end
def low_risk?(response)
risk_band = response.parsed_response&.dig('session_risk', 'risk_band')
risk_band = response&.dig('session_risk', 'risk_band')
risk_band.present? ? risk_band != 'High' : true
end
def allowlisted?(response)
telltale_list = response.parsed_response&.dig('session_details', 'telltale_list') || []
telltale_list = response&.dig('session_details', 'telltale_list') || []
telltale_list.include?(ALLOWLIST_TELLTALE)
end
end
......
......@@ -4,8 +4,9 @@ require 'spec_helper'
RSpec.describe Arkose::UserVerificationService do
let(:session_token) { '22612c147bb418c8.2570749403' }
let(:userid) { '1999' }
let(:service) { Arkose::UserVerificationService.new(session_token: session_token, userid: userid) }
let_it_be_with_reload(:user) { create(:user, id: '1999') }
let(:service) { Arkose::UserVerificationService.new(session_token: session_token, user: user) }
let(:response) { instance_double(HTTParty::Response, success?: true, code: 200, parsed_response: arkose_ec_response) }
subject { service.execute }
......@@ -29,6 +30,17 @@ RSpec.describe Arkose::UserVerificationService do
expect(subject).to be_truthy
end
it 'adds arkose data to custom attributes' do
allow(Gitlab::HTTP).to receive(:perform_request).and_return(response)
subject
expect(user.custom_attributes.count).to eq(4)
expect(user.custom_attributes.find_by(key: 'arkose_session').value).to eq('22612c147bb418c8.2570749403')
expect(user.custom_attributes.find_by(key: 'arkose_risk_band').value).to eq('Low')
expect(user.custom_attributes.find_by(key: 'arkose_global_score').value).to eq('0')
expect(user.custom_attributes.find_by(key: 'arkose_custom_score').value).to eq('0')
end
context 'when the risk score is high' do
let(:arkose_ec_response) { Gitlab::Json.parse(File.read(Rails.root.join('ee/spec/fixtures/arkose/successfully_solved_ec_response_high_risk.json'))) }
......
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