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 ...@@ -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