Commit 5afeadbb authored by Marcos Rocha's avatar Marcos Rocha Committed by Stan Hu

Add arkose verify response details to custom attributes

This MR adds the arkose verify details to the user's custom attributes

Changelog: added
MR:
EE: true
parent fd61c791
......@@ -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