Skip to content
代码片段 群组 项目
提交 b2b0ff47 编辑于 作者: Eugie Limpin's avatar Eugie Limpin 提交者: Stan Hu
浏览文件

Create Arkose::VerifyResponse class

Arkose::VerifyResponse class contains methods that were previously
implemented in Arkose::UserVerificationService. These methods
use/returns values from the JSON response returned by Arkose Verify API
endpoint. They are extracted from Arkose::UserVerificationService to
make them reusable.
上级 123ecd49
No related branches found
No related tags found
无相关合并请求
# frozen_string_literal: true
module Arkose
class UserVerificationService
attr_reader :url, :session_token, :user
attr_reader :url, :session_token, :user, :response
ARKOSE_LABS_DEFAULT_NAMESPACE = 'client'
ARKOSE_LABS_DEFAULT_SUBDOMAIN = 'verify-api'
ALLOWLIST_TELLTALE = 'gitlab1-whitelist-qa-team'
def initialize(session_token:, user:)
@session_token = session_token
......@@ -13,14 +12,16 @@ def initialize(session_token:, user:)
end
def execute
response = Gitlab::HTTP.perform_request(Net::HTTP::Post, arkose_verify_url, body: body).parsed_response
logger.info(build_message(response))
json_response = Gitlab::HTTP.perform_request(Net::HTTP::Post, arkose_verify_url, body: body).parsed_response
@response = VerifyResponse.new(json_response)
return false if invalid_token(response)
logger.info(build_message)
add_or_update_arkose_attributes(response)
return false if response.invalid_token?
allowlisted?(response) || (challenge_solved?(response) && low_risk?(response))
add_or_update_arkose_attributes
response.allowlisted? || (response.challenge_solved? && response.low_risk?)
rescue StandardError => error
payload = { session_token: session_token, log_data: user.id }
Gitlab::ExceptionLogFormatter.format!(error, payload)
......@@ -32,53 +33,23 @@ def execute
private
def add_or_update_arkose_attributes(response)
def add_or_update_arkose_attributes
return if Gitlab::Database.read_only?
custom_attributes = custom_attributes(response)
UserCustomAttribute.upsert_custom_attributes(custom_attributes)
end
def custom_attributes(response)
def custom_attributes
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.push({ key: 'arkose_session', value: response.session_id })
custom_attributes.push({ key: 'arkose_risk_band', value: response.risk_band })
custom_attributes.push({ key: 'arkose_global_score', value: response.global_score })
custom_attributes.push({ key: 'arkose_custom_score', value: response.custom_score })
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 risk_category(response)
response&.dig('session_risk', 'risk_category') || 'Unavailable'
end
def global_telltale_list(response)
response&.dig('session_risk', 'global', 'telltales') || 'Unavailable'
end
def custom_telltale_list(response)
response&.dig('session_risk', 'custom', 'telltales') || 'Unavailable'
end
def body
{
private_key: Settings.arkose_private_api_key,
......@@ -91,49 +62,28 @@ def logger
Gitlab::AppLogger
end
def build_message(response)
def build_message
Gitlab::ApplicationContext.current.symbolize_keys.merge(
{
message: 'Arkose verify response',
response: response,
response: response.response,
username: user.username
}.merge(arkose_payload(response))
}.merge(arkose_payload)
)
end
def arkose_payload(response)
def arkose_payload
{
'arkose.session_id': session_id(response),
'arkose.global_score': global_score(response),
'arkose.global_telltale_list': global_telltale_list(response),
'arkose.custom_score': custom_score(response),
'arkose.custom_telltale_list': custom_telltale_list(response),
'arkose.risk_band': risk_band(response),
'arkose.risk_category': risk_category(response)
'arkose.session_id': response.session_id,
'arkose.global_score': response.global_score,
'arkose.global_telltale_list': response.global_telltale_list,
'arkose.custom_score': response.custom_score,
'arkose.custom_telltale_list': response.custom_telltale_list,
'arkose.risk_band': response.risk_band,
'arkose.risk_category': response.risk_category
}
end
def invalid_token(response)
response&.key?('error')
end
def challenge_solved?(response)
solved = response&.dig('session_details', 'solved')
solved.nil? ? true : solved
end
def low_risk?(response)
return true unless Feature.enabled?(:arkose_labs_prevent_login)
risk_band = risk_band(response)
risk_band.present? ? risk_band != 'High' : true
end
def allowlisted?(response)
telltale_list = response&.dig('session_details', 'telltale_list') || []
telltale_list.include?(ALLOWLIST_TELLTALE)
end
def arkose_verify_url
arkose_labs_namespace = ::Gitlab::CurrentSettings.arkose_labs_namespace
subdomain = if arkose_labs_namespace == ARKOSE_LABS_DEFAULT_NAMESPACE
......
# frozen_string_literal: true
module Arkose
class VerifyResponse
attr_reader :response
ALLOWLIST_TELLTALE = 'gitlab1-whitelist-qa-team'
def initialize(response)
@response = response
end
def invalid_token?
response&.key?('error')
end
def error
response["error"]
end
def challenge_solved?
solved = response&.dig('session_details', 'solved')
solved.nil? ? true : solved
end
def low_risk?
return true unless Feature.enabled?(:arkose_labs_prevent_login)
risk_band.present? ? risk_band != 'High' : true
end
def allowlisted?
telltale_list = response&.dig('session_details', 'telltale_list') || []
telltale_list.include?(ALLOWLIST_TELLTALE)
end
def custom_score
response&.dig('session_risk', 'custom', 'score') || 0
end
def global_score
response&.dig('session_risk', 'global', 'score') || 0
end
def risk_band
response&.dig('session_risk', 'risk_band') || 'Unavailable'
end
def session_id
response&.dig('session_details', 'session') || 'Unavailable'
end
def risk_category
response&.dig('session_risk', 'risk_category') || 'Unavailable'
end
def global_telltale_list
response&.dig('session_risk', 'global', 'telltales') || 'Unavailable'
end
def custom_telltale_list
response&.dig('session_risk', 'custom', 'telltales') || 'Unavailable'
end
end
end
{
"session_details": {
"solved": false,
"session": "22612c147bb418c8.2570749403",
"session_created": "2021-08-29T23:13:03+00:00",
"check_answer": "2021-08-29T23:13:16+00:00",
"verified": "2021-08-30T00:19:32+00:00",
"attempted": true,
"security_level": 30,
"session_is_legit": false,
"previously_verified": true,
"session_timed_out": true,
"suppress_limited": false,
"theme_arg_invalid": false,
"suppressed": false,
"punishable_actioned": false,
"telltale_user": "eng-1362-game3-py-0.",
"failed_low_sec_validation": false,
"lowsec_error": null,
"lowsec_level_denied": null,
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
"ip_rep_list": null,
"game_number_limit_reached": false,
"user_language_shown": "en",
"telltale_list": [
"gitlab1-whitelist-qa-team"
],
"optional": null
},
"fingerprint": {
"browser_characteristics": {
"browser_name": "Chrome",
"browser_version": "92.0.4515.159",
"color_depth": 24,
"session_storage": false,
"indexed_database": false,
"canvas_fingerprint": 1652956012
},
"device_characteristics": {
"operating_system": null,
"operating_system_version": null,
"screen_resolution": [
1920,
1080
],
"max_resolution_supported": [
1920,
1057
],
"behavior": false,
"cpu_class": "unknown",
"platform": "MacIntel",
"touch_support": false,
"hardware_concurrency": 8
},
"user_preferences": {
"timezone_offset": -600
}
},
"ip_intelligence": {
"user_ip": "10.211.121.196",
"is_tor": false,
"is_vpn": true,
"is_proxy": true,
"is_bot": true,
"country": "AU",
"region": "New South Wales",
"city": "Sydney",
"isp": "Amazon.com",
"public_access_point": false,
"connection_type": "Data Center",
"latitude": "-38.85120035",
"longitude": "106.21220398",
"timezone": "Australia/Sydney"
}
}
{
"error": "DENIED ACCESS",
"verified": "2022-08-12T07:57:31Z"
}
\ No newline at end of file
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Arkose::VerifyResponse do
def parse_json(file_path)
Gitlab::Json.parse(File.read(Rails.root.join(file_path)))
end
let(:invalid_token_response) do
parse_json('ee/spec/fixtures/arkose/invalid_token.json')
end
let(:unsolved_challenge_response) do
parse_json('ee/spec/fixtures/arkose/failed_ec_response.json')
end
let(:low_risk_response) do
parse_json('ee/spec/fixtures/arkose/successfully_solved_ec_response.json')
end
let(:high_risk_response) do
parse_json('ee/spec/fixtures/arkose/successfully_solved_ec_response_high_risk.json')
end
let(:allowlisted_response) do
parse_json('ee/spec/fixtures/arkose/allowlisted_response.json')
end
describe '#invalid_token?' do
subject { described_class.new(json_response).invalid_token? }
context 'when token is invalid' do
let(:json_response) { invalid_token_response }
it { is_expected.to eq true }
end
context 'when token is valid' do
let(:json_response) { unsolved_challenge_response }
it { is_expected.to eq false }
end
end
describe '#error' do
let(:json_response) { invalid_token_response }
subject { described_class.new(json_response).error }
it { is_expected.to eq 'DENIED ACCESS' }
end
describe '#challenge_solved?' do
subject { described_class.new(json_response).challenge_solved? }
context 'when response does not contain solved data' do
let(:json_response) { Gitlab::Json.parse("{}") }
it { is_expected.to eq true }
end
context 'when response contains solved data' do
let(:json_response) { unsolved_challenge_response }
it { is_expected.to eq false }
end
end
describe '#low_risk?' do
subject { described_class.new(json_response).low_risk? }
context 'when arkose_labs_prevent_login feature flag is disabled' do
let(:json_response) { Gitlab::Json.parse("{}") }
before do
stub_feature_flags(arkose_labs_prevent_login: false)
end
it { is_expected.to eq true }
end
context 'when response does not contain session_risk.risk_band data' do
let(:json_response) { Gitlab::Json.parse("{}") }
it { is_expected.to eq true }
end
context 'when response contains session_risk.risk_band != "High"' do
let(:json_response) { low_risk_response }
it { is_expected.to eq true }
end
context 'when response contains session_risk.risk_band == "High"' do
let(:json_response) { high_risk_response }
it { is_expected.to eq false }
end
end
describe '#allowlisted?' do
subject { described_class.new(json_response).allowlisted? }
context 'when session_details.telltale_list data includes ALLOWLIST_TELLTALE' do
let(:json_response) { allowlisted_response }
it { is_expected.to eq true }
end
context 'when session_details.telltale_list data does not include ALLOWLIST_TELLTALE' do
let(:json_response) { high_risk_response }
it { is_expected.to eq false }
end
context 'when response does not include session_details.telltale_list data' do
let(:json_response) { Gitlab::Json.parse("{}") }
it { is_expected.to eq false }
end
end
describe 'other methods' do
using RSpec::Parameterized::TableSyntax
subject(:response) { described_class.new(json_response) }
context 'when response has the correct data' do
let(:global_telltale_list) do
[
{ "name" => "g-h-cfp-1000000000", "weight" => "7" },
{ "name" => "g-os-impersonation-win", "weight" => "8" }
]
end
let(:custom_telltale_list) do
[
{ "name" => "outdated-browser-customer-2", "weight" => "100" },
{ "name" => "outdated-os-customer", "weight" => "100" }
]
end
where(:method, :expected_value) do
:custom_score | "100"
:global_score | "15"
:risk_band | "High"
:session_id | "22612c147bb418c8.2570749403"
:risk_category | "BOT-STD"
:global_telltale_list | lazy { global_telltale_list }
:custom_telltale_list | lazy { custom_telltale_list }
end
with_them do
let(:json_response) { high_risk_response }
it 'succeeds' do
expect(response.public_send(method)).to eq expected_value
end
end
end
context 'when response does not have the correct data' do
where(:method, :expected_value) do
:custom_score | 0
:global_score | 0
:risk_band | 'Unavailable'
:session_id | 'Unavailable'
:risk_category | 'Unavailable'
:global_telltale_list | 'Unavailable'
:custom_telltale_list | 'Unavailable'
end
with_them do
let(:json_response) { Gitlab::Json.parse("{}") }
it 'succeeds' do
expect(response.public_send(method)).to eq expected_value
end
end
end
end
end
......@@ -91,7 +91,7 @@
context 'when the session is allowlisted' do
let(:arkose_ec_response) do
json = Gitlab::Json.parse(File.read(Rails.root.join('ee/spec/fixtures/arkose/successfully_solved_ec_response.json')))
json['session_details']['telltale_list'].push(Arkose::UserVerificationService::ALLOWLIST_TELLTALE)
json['session_details']['telltale_list'].push(Arkose::VerifyResponse::ALLOWLIST_TELLTALE)
json
end
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册