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 ...@@ -5,4 +5,14 @@ class UserCustomAttribute < ApplicationRecord
validates :user_id, :key, :value, presence: true validates :user_id, :key, :value, presence: true
validates :key, uniqueness: { scope: [:user_id] } 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 end
...@@ -96,7 +96,7 @@ module EE ...@@ -96,7 +96,7 @@ module EE
return unless user.present? 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 increment_successful_login_captcha_counter
else else
increment_failed_login_captcha_counter increment_failed_login_captcha_counter
......
# frozen_string_literal: true # frozen_string_literal: true
module Arkose module Arkose
class UserVerificationService class UserVerificationService
attr_reader :url, :session_token, :userid attr_reader :url, :session_token, :user
VERIFY_URL = 'http://verify-api.arkoselabs.com/api/v4/verify' VERIFY_URL = 'http://verify-api.arkoselabs.com/api/v4/verify'
ALLOWLIST_TELLTALE = 'gitlab1-whitelist-qa-team' ALLOWLIST_TELLTALE = 'gitlab1-whitelist-qa-team'
def initialize(session_token:, userid:) def initialize(session_token:, user:)
@session_token = session_token @session_token = session_token
@userid = userid @user = user
end end
def execute def execute
response = Gitlab::HTTP.perform_request(Net::HTTP::Post, VERIFY_URL, body: body) response = Gitlab::HTTP.perform_request(Net::HTTP::Post, VERIFY_URL, body: body).parsed_response
logger.info(build_message("Arkose verify response: #{response.parsed_response}")) logger.info(build_message("Arkose verify response: #{response}"))
return false if invalid_token(response) return false if invalid_token(response)
add_or_update_arkose_attributes(response)
allowlisted?(response) || (challenge_solved?(response) && low_risk?(response)) allowlisted?(response) || (challenge_solved?(response) && low_risk?(response))
rescue StandardError => error 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::ExceptionLogFormatter.format!(error, payload)
Gitlab::ErrorTracking.track_exception(error) Gitlab::ErrorTracking.track_exception(error)
logger.error(build_message("Error verifying user on Arkose: #{payload}")) logger.error(build_message("Error verifying user on Arkose: #{payload}"))
...@@ -29,11 +31,46 @@ module Arkose ...@@ -29,11 +31,46 @@ module Arkose
private 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 def body
{ {
private_key: Settings.arkose['private_key'], private_key: Settings.arkose['private_key'],
session_token: session_token, session_token: session_token,
log_data: userid log_data: user.id
} }
end end
...@@ -46,21 +83,21 @@ module Arkose ...@@ -46,21 +83,21 @@ module Arkose
end end
def invalid_token(response) def invalid_token(response)
response.parsed_response&.key?('error') response&.key?('error')
end end
def challenge_solved?(response) def challenge_solved?(response)
solved = response.parsed_response&.dig('session_details', 'solved') solved = response&.dig('session_details', 'solved')
solved.nil? ? true : solved solved.nil? ? true : solved
end end
def low_risk?(response) 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 risk_band.present? ? risk_band != 'High' : true
end end
def allowlisted?(response) 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) telltale_list.include?(ALLOWLIST_TELLTALE)
end end
end end
......
...@@ -4,8 +4,9 @@ require 'spec_helper' ...@@ -4,8 +4,9 @@ require 'spec_helper'
RSpec.describe Arkose::UserVerificationService do RSpec.describe Arkose::UserVerificationService do
let(:session_token) { '22612c147bb418c8.2570749403' } let(:session_token) { '22612c147bb418c8.2570749403' }
let(:userid) { '1999' } let_it_be_with_reload(:user) { create(:user, id: '1999') }
let(:service) { Arkose::UserVerificationService.new(session_token: session_token, userid: userid) }
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) } let(:response) { instance_double(HTTParty::Response, success?: true, code: 200, parsed_response: arkose_ec_response) }
subject { service.execute } subject { service.execute }
...@@ -29,6 +30,17 @@ RSpec.describe Arkose::UserVerificationService do ...@@ -29,6 +30,17 @@ RSpec.describe Arkose::UserVerificationService do
expect(subject).to be_truthy expect(subject).to be_truthy
end 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 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'))) } 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