Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ProgressiveProofer refactor 1/N: ThreatMetrix #11420

Merged
merged 5 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions app/services/proofing/resolution/plugins/threat_metrix_plugin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

module Proofing
module Resolution
module Plugins
class ThreatMetrixPlugin
def call(
applicant_pii:,
current_sp:,
request_ip:,
threatmetrix_session_id:,
timer:,
user_email:
)
unless FeatureManagement.proofing_device_profiling_collecting_enabled?
return threatmetrix_disabled_result
end

# The API call will fail without a session ID, so do not attempt to make
# it to avoid leaking data when not required.
return threatmetrix_id_missing_result if threatmetrix_session_id.blank?
return threatmetrix_pii_missing_result if applicant_pii.blank?

ddp_pii = applicant_pii.merge(
threatmetrix_session_id: threatmetrix_session_id,
email: user_email,
request_id: request_ip,
)

timer.time('threatmetrix') do
proofer.proof(ddp_pii)
end.tap do |result|
Db::SpCost::AddSpCost.call(
current_sp, :threatmetrix,
transaction_id: result.transaction_id
)
matthinz marked this conversation as resolved.
Show resolved Hide resolved
end
end

def proofer
@proofer ||=
if IdentityConfig.store.lexisnexis_threatmetrix_mock_enabled
Proofing::Mock::DdpMockClient.new
else
Proofing::LexisNexis::Ddp::Proofer.new(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up. Will be updating the config to add a "policy" attribute since we have decided for Authentication we will have a different policy. But otherwise. Taking a look this shouldn't affect our implementation much. may be a merge conflict depending on whats merged first.

api_key: IdentityConfig.store.lexisnexis_threatmetrix_api_key,
org_id: IdentityConfig.store.lexisnexis_threatmetrix_org_id,
base_url: IdentityConfig.store.lexisnexis_threatmetrix_base_url,
)
end
end

def threatmetrix_disabled_result
Proofing::DdpResult.new(
success: true,
client: 'tmx_disabled',
review_status: 'pass',
)
end

def threatmetrix_pii_missing_result
Proofing::DdpResult.new(
success: false,
client: 'tmx_pii_missing',
review_status: 'reject',
)
end

def threatmetrix_id_missing_result
Proofing::DdpResult.new(
success: false,
client: 'tmx_session_id_missing',
review_status: 'reject',
)
end
end
end
end
end
84 changes: 15 additions & 69 deletions app/services/proofing/resolution/progressive_proofer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ module Resolution
# 2. The user has only provided one address for their residential and identity document
# address or separate residential and identity document addresses
class ProgressiveProofer
attr_reader :applicant_pii,
:request_ip,
:threatmetrix_session_id,
:timer,
:user_email,
:current_sp
attr_reader :applicant_pii, :timer, :current_sp
attr_reader :threatmetrix_plugin

def initialize
@threatmetrix_plugin = Plugins::ThreatMetrixPlugin.new
end

# @param [Hash] applicant_pii keys are symbols and values are strings, confidential user info
# @param [Boolean] ipp_enrollment_in_progress flag that indicates if user will have
Expand All @@ -33,14 +33,19 @@ def proof(
current_sp:
)
@applicant_pii = applicant_pii.except(:best_effort_phone_number_for_socure)
@request_ip = request_ip
@threatmetrix_session_id = threatmetrix_session_id
@timer = timer
@user_email = user_email
@ipp_enrollment_in_progress = ipp_enrollment_in_progress
@current_sp = current_sp

@device_profiling_result = proof_with_threatmetrix_if_needed
device_profiling_result = threatmetrix_plugin.call(
applicant_pii:,
current_sp:,
threatmetrix_session_id:,
request_ip:,
timer:,
user_email:,
)

@residential_instant_verify_result = proof_residential_address_if_needed
@instant_verify_result = proof_id_address_with_lexis_nexis_if_needed
@state_id_result = proof_id_with_aamva_if_needed
Expand All @@ -64,28 +69,6 @@ def proof(
:instant_verify_result,
:state_id_result

def proof_with_threatmetrix_if_needed
unless FeatureManagement.proofing_device_profiling_collecting_enabled?
return threatmetrix_disabled_result
end

# The API call will fail without a session ID, so do not attempt to make
# it to avoid leaking data when not required.
return threatmetrix_id_missing_result if threatmetrix_session_id.blank?
return threatmetrix_pii_missing_result if applicant_pii.blank?

ddp_pii = applicant_pii.dup
ddp_pii[:threatmetrix_session_id] = threatmetrix_session_id
ddp_pii[:email] = user_email
ddp_pii[:request_ip] = request_ip

timer.time('threatmetrix') do
lexisnexis_ddp_proofer.proof(ddp_pii)
end.tap do |result|
add_sp_cost(:threatmetrix, result.transaction_id)
end
end

def proof_residential_address_if_needed
return residential_address_unnecessary_result unless ipp_enrollment_in_progress?

Expand Down Expand Up @@ -173,30 +156,6 @@ def ipp_enrollment_in_progress?
@ipp_enrollment_in_progress
end

def threatmetrix_disabled_result
Proofing::DdpResult.new(
success: true,
client: 'tmx_disabled',
review_status: 'pass',
)
end

def threatmetrix_pii_missing_result
Proofing::DdpResult.new(
success: false,
client: 'tmx_pii_missing',
review_status: 'reject',
)
end

def threatmetrix_id_missing_result
Proofing::DdpResult.new(
success: false,
client: 'tmx_session_id_missing',
review_status: 'reject',
)
end

def out_of_aamva_jurisdiction_result
Proofing::StateIdResult.new(
errors: {},
Expand All @@ -206,19 +165,6 @@ def out_of_aamva_jurisdiction_result
)
end

def lexisnexis_ddp_proofer
@lexisnexis_ddp_proofer ||=
if IdentityConfig.store.lexisnexis_threatmetrix_mock_enabled
Proofing::Mock::DdpMockClient.new
else
Proofing::LexisNexis::Ddp::Proofer.new(
api_key: IdentityConfig.store.lexisnexis_threatmetrix_api_key,
org_id: IdentityConfig.store.lexisnexis_threatmetrix_org_id,
base_url: IdentityConfig.store.lexisnexis_threatmetrix_base_url,
)
end
end

def resolution_proofer
@resolution_proofer ||=
if IdentityConfig.store.proofer_mock_fallback
Expand Down
108 changes: 108 additions & 0 deletions spec/services/proofing/resolution/plugins/threatmetrix_plugin_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
require 'rails_helper'

RSpec.describe Proofing::Resolution::Plugins::ThreatMetrixPlugin do
let(:applicant_pii) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN }
let(:current_sp) { build(:service_provider) }
let(:proofer_result) do
instance_double(Proofing::DdpResult, success?: true, transaction_id: 'ddp-123')
end
let(:request_ip) { Faker::Internet.ip_v4_address }
let(:threatmetrix_session_id) { 'cool-session-id' }
let(:user_email) { Faker::Internet.email }

subject(:plugin) do
described_class.new
end

before do
allow(IdentityConfig.store).to receive(:lexisnexis_threatmetrix_mock_enabled).
and_return(false)
allow(plugin.proofer).to receive(:proof).and_return(proofer_result)
end

describe '#call' do
def sp_cost_count
SpCost.where(cost_type: :threatmetrix, issuer: current_sp.issuer).count
end

subject(:call) do
plugin.call(
applicant_pii:,
current_sp:,
request_ip:,
threatmetrix_session_id:,
timer: JobHelpers::Timer.new,
user_email:,
)
end

context 'ThreatMetrix is enabled' do
before do
allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?).
and_return(true)
end

it 'calls the ThreatMetrix proofer' do
call
expect(plugin.proofer).to have_received(:proof)
end

it 'creates a ThreatMetrix associated cost' do
expect { call }.to change { sp_cost_count }.to(1)
end

context 'session id is missing' do
let(:threatmetrix_session_id) { nil }

it 'does not call the ThreatMetrix proofer' do
expect(plugin.proofer).not_to receive(:proof)
call
end

it 'returns a failed result' do
call.tap do |result|
expect(result.success).to be(false)
expect(result.client).to eq('tmx_session_id_missing')
expect(result.review_status).to eq('reject')
end
end
end

context 'pii is missing' do
let(:applicant_pii) { {} }

it 'does not call the ThreatMetrix proofer' do
expect(plugin.proofer).not_to receive(:proof)
call
end

it 'returns a failed result' do
call.tap do |result|
expect(result.success).to be(false)
expect(result.client).to eq('tmx_pii_missing')
expect(result.review_status).to eq('reject')
end
end
end
end

context 'ThreatMetrix is disabled' do
before do
allow(FeatureManagement).to receive(:proofing_device_profiling_collecting_enabled?).
and_return(false)
end

it 'returns a disabled result' do
call.tap do |result|
expect(result.success).to be(true)
expect(result.client).to eq('tmx_disabled')
expect(result.review_status).to eq('pass')
end
end

it 'does not create a ThreatMetrix associated cost' do
expect { call }.not_to change { sp_cost_count }
end
end
end
end
Loading