Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7ab60b3
LG-7010 Add new DDP Proofer
stevegsa Aug 3, 2022
723a1f9
Optional fields
stevegsa Aug 3, 2022
9e1eefa
Lint
stevegsa Aug 3, 2022
f9fdca8
Lint
stevegsa Aug 3, 2022
cecfb90
Fix
stevegsa Aug 3, 2022
603eb50
LG-7012 Integrate DDP Proofer
stevegsa Aug 8, 2022
d69f5de
rm failed response
stevegsa Aug 8, 2022
8844ccf
Lint
stevegsa Aug 8, 2022
7c6db18
use_lexisnexis_ddp_threatmetrix_before_rdp_instant_verify?
stevegsa Aug 8, 2022
6be75d8
Add results
stevegsa Aug 9, 2022
2909f7a
fix session id
stevegsa Aug 9, 2022
d641b54
for now comment out replace_lexisnexis_rdp_instant_verify_with_ddp
stevegsa Aug 10, 2022
ad6b150
Allow alphanumeric on state_id_number
stevegsa Aug 10, 2022
e49e029
Use unambiguous ISO format date in spec
stevegsa Aug 10, 2022
2251142
Nil dob
stevegsa Aug 10, 2022
ccd9aa3
Proceed to instantverify even if user does not pass fraud check also …
stevegsa Aug 10, 2022
0057afe
Ddp mock client
stevegsa Aug 11, 2022
b084e2d
Specs
stevegsa Aug 11, 2022
23e2d3a
rm instance double
stevegsa Aug 11, 2022
1b3e44c
Logging
stevegsa Aug 12, 2022
dc94760
logging
stevegsa Aug 12, 2022
50caa6e
Hookup email and session id from feeder PR 6694
stevegsa Aug 12, 2022
bd71ed6
Bugs
stevegsa Aug 12, 2022
66a05d9
Bugs
stevegsa Aug 12, 2022
cc76729
Bug
stevegsa Aug 12, 2022
8c1a77a
Bug
stevegsa Aug 12, 2022
fa60003
Bug
stevegsa Aug 12, 2022
0564dbd
More log info
stevegsa Aug 12, 2022
ffa5095
Bugs
stevegsa Aug 12, 2022
13c3037
Default values for job
stevegsa Aug 12, 2022
ed933ed
rm replace_lexisnexis_rdp_instant_verify_with_ddp
stevegsa Aug 12, 2022
e8a6a19
changelog: Upcoming Features, Identity verification, Add/Integrate ne…
stevegsa Aug 12, 2022
e6ab5ca
fix mock
stevegsa Aug 14, 2022
1c84082
Fix required fields
stevegsa Aug 14, 2022
a311bd3
Fix session_id
stevegsa Aug 14, 2022
528673f
Standardize app.yml.default with front end naming
stevegsa Aug 14, 2022
2bc12ba
Goodies
stevegsa Aug 15, 2022
02cccb8
Suppress phone
stevegsa Aug 15, 2022
4fc272d
Dup line
stevegsa Aug 15, 2022
97f2e5b
Allow full zip
stevegsa Aug 15, 2022
e9a8a62
big zip
stevegsa Aug 15, 2022
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
2 changes: 2 additions & 0 deletions app/controllers/idv/gpo_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ def enqueue_job
document_capture_session,
should_proof_state_id: false,
trace_id: amzn_trace_id,
user_id: current_user.id,
threatmetrix_session_id: nil,
)
end

Expand Down
70 changes: 61 additions & 9 deletions app/jobs/resolution_proofing_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ResolutionProofingJob < ApplicationJob
)

def perform(result_id:, encrypted_arguments:, trace_id:, should_proof_state_id:,
dob_year_only:)
dob_year_only:, user_id: nil, threatmetrix_session_id: nil)
timer = JobHelpers::Timer.new

raise_stale_job! if stale_job?(enqueued_at)
Expand All @@ -25,6 +25,15 @@ def perform(result_id:, encrypted_arguments:, trace_id:, should_proof_state_id:,

applicant_pii = decrypted_args[:applicant_pii]

threatmetrix_result = nil
if use_lexisnexis_ddp_threatmetrix_before_rdp_instant_verify?
user = User.find_by(id: user_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so I see that we're passing user_id, but I think that ActiveJob will correctly serialize/deserialize ActiveRecord models, we should just pass user if we wanted to I think

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I modeled it after AddressProofingJob which takes user_id

threatmetrix_result = proof_lexisnexis_ddp_with_threatmetrix(
applicant_pii, user, threatmetrix_session_id
)
log_threatmetrix_info(threatmetrix_result, user)
end

callback_log_data = if dob_year_only && should_proof_state_id
proof_aamva_then_lexisnexis_dob_only(
timer: timer,
Expand All @@ -39,22 +48,48 @@ def perform(result_id:, encrypted_arguments:, trace_id:, should_proof_state_id:,
)
end

add_threatmetrix_result_to_callback_result(callback_log_data.result, threatmetrix_result)

document_capture_session = DocumentCaptureSession.new(result_id: result_id)
document_capture_session.store_proofing_result(callback_log_data.result)
ensure
logger.info(
{
name: 'ProofResolution',
trace_id: trace_id,
resolution_success: callback_log_data&.resolution_success,
state_id_success: callback_log_data&.state_id_success,
timing: timer.results,
}.to_json,
logger_info_hash(
name: 'ProofResolution',
trace_id: trace_id,
resolution_success: callback_log_data&.resolution_success,
state_id_success: callback_log_data&.state_id_success,
timing: timer.results,
)
end

private

def log_threatmetrix_info(threatmetrix_result, user)
logger_info_hash(
name: 'ThreatMetrix',
user_id: user&.uuid,
threatmetrix_request_id: threatmetrix_result.transaction_id,
threatmetrix_success: threatmetrix_result.success?,
)
end

def logger_info_hash(hash)
logger.info(hash.to_json)
end
Comment on lines +76 to +78
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am not convinced this helper method is worth it?

Copy link
Copy Markdown
Contributor Author

@stevegsa stevegsa Aug 12, 2022

Choose a reason for hiding this comment

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

was difficult to test multiple logger calls especially because old instant verify code was doing a partial check. this made it much more simple to test

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it's also a tad bit DRYer


def add_threatmetrix_result_to_callback_result(callback_log_data_result, threatmetrix_result)
callback_log_data_result[:threatmetrix_success] = threatmetrix_result.success?
callback_log_data_result[:threatmetrix_request_id] = threatmetrix_result.transaction_id
end

def proof_lexisnexis_ddp_with_threatmetrix(applicant_pii, user, threatmetrix_session_id)
return unless applicant_pii
ddp_pii = applicant_pii.dup
ddp_pii[:threatmetrix_session_id] = threatmetrix_session_id
ddp_pii[:email] = user&.confirmed_email_addresses&.first&.email
lexisnexis_ddp_proofer.proof(ddp_pii)
end

# @return [CallbackLogData]
def proof_lexisnexis_then_aamva(timer:, applicant_pii:, should_proof_state_id:)
proofer_result = timer.time('resolution') do
Expand Down Expand Up @@ -202,6 +237,19 @@ def resolution_proofer
end
end

def lexisnexis_ddp_proofer
@lexisnexis_ddp_proofer ||=
if IdentityConfig.store.proofer_mock_fallback
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 state_id_proofer
@state_id_proofer ||=
if IdentityConfig.store.proofer_mock_fallback
Expand All @@ -218,4 +266,8 @@ def state_id_proofer
)
end
end

def use_lexisnexis_ddp_threatmetrix_before_rdp_instant_verify?
IdentityConfig.store.lexisnexis_threatmetrix_enabled
end
end
8 changes: 7 additions & 1 deletion app/services/idv/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ def initialize(applicant)
end

def proof_resolution(
document_capture_session, should_proof_state_id:, trace_id:
document_capture_session,
should_proof_state_id:,
trace_id:,
user_id:,
threatmetrix_session_id:
)
document_capture_session.create_proofing_session

Expand All @@ -19,6 +23,8 @@ def proof_resolution(
dob_year_only: IdentityConfig.store.proofing_send_partial_dob,
trace_id: trace_id,
result_id: document_capture_session.result_id,
user_id: user_id,
threatmetrix_session_id: threatmetrix_session_id,
}

if IdentityConfig.store.ruby_workers_idv_enabled
Expand Down
4 changes: 4 additions & 0 deletions app/services/idv/steps/verify_base_step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def add_proofing_costs(results)
if stage == :resolution
# transaction_id comes from ConversationId
add_cost(:lexis_nexis_resolution, transaction_id: hash[:transaction_id])
tmx_id = hash[:threatmetrix_request_id]
add_cost(:threatmetrix, transaction_id: tmx_id) if tmx_id
elsif stage == :state_id
process_aamva(hash[:transaction_id])
end
Expand Down Expand Up @@ -176,6 +178,8 @@ def enqueue_job
document_capture_session,
should_proof_state_id: should_use_aamva?(pii),
trace_id: amzn_trace_id,
user_id: user_id,
threatmetrix_session_id: flow_session[:threatmetrix_session_id],
)
end

Expand Down
49 changes: 49 additions & 0 deletions app/services/proofing/lexis_nexis/ddp/proofer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module Proofing
module LexisNexis
module Ddp
class Proofer < LexisNexis::Proofer
vendor_name 'lexisnexis:ddp'

required_attributes :state_id_number,
:threatmetrix_session_id,
:state_id_number,
:first_name,
:last_name,
:dob,
:ssn,
:address1,
:city,
:state,
:zipcode

optional_attributes :address2, :phone, :email

stage :resolution

proof do |applicant, result|
proof_applicant(applicant, result)
end

def send_verification_request(applicant)
VerificationRequest.new(config: config, applicant: applicant).send
end

def proof_applicant(applicant, result)
response = send_verification_request(applicant)
process_response(response, result)
end

private

def process_response(response, result)
body = response.response_body
result.transaction_id = body['request_id']
request_result = body['request_result']
review_status = body['review_status']
result.add_error(:request_result, request_result) unless request_result == 'success'
result.add_error(:review_status, review_status) unless review_status == 'pass'
end
end
end
end
end
46 changes: 46 additions & 0 deletions app/services/proofing/lexis_nexis/ddp/verification_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Proofing
module LexisNexis
module Ddp
class VerificationRequest < Request
private

def build_request_body
{
api_key: config.api_key,
org_id: config.org_id,
account_address_street1: applicant[:address1],
account_address_street2: applicant[:address2] || '',
account_address_city: applicant[:city],
account_address_state: applicant[:state],
account_address_country: 'US',
account_address_zip: applicant[:zipcode],
account_date_of_birth: applicant[:dob] ?
Date.parse(applicant[:dob]).strftime('%Y%m%d') : '',
account_email: applicant[:email],
account_first_name: applicant[:first_name],
account_last_name: applicant[:last_name],
account_telephone: '', # applicant[:phone], decision was made not to send phone
drivers_license_number_hash: applicant[:state_id_number] ?
OpenSSL::Digest::SHA256.hexdigest(applicant[:state_id_number].gsub(/\W/, '')) : '',
Comment on lines +23 to +24
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd consider extracting methods for these _hash attributes so we could unit test them, which would allow us to check and verify the regexes like state_id_number_for_hash or ssn_for_hash

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agree. This particular code is covered but it's quick and dirty.

event_type: 'ACCOUNT_CREATION',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is there a list of other event types? this is more like an account upgrade typically than an account create

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Screen Shot 2022-08-15 at 2 36 08 PM

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

seems to be the best option. ie create a verified profile

service_type: 'all',
session_id: applicant[:threatmetrix_session_id],
ssn_hash: OpenSSL::Digest::SHA256.hexdigest(applicant[:ssn].gsub(/\D/, '')),
}.to_json
end

def metric_name
'lexis_nexis_ddp'
end

def url_request_path
'/api/session-query'
end

def timeout
IdentityConfig.store.lexisnexis_threatmetrix_timeout
end
end
end
end
end
2 changes: 2 additions & 0 deletions app/services/proofing/lexis_nexis/proofer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class Proofer < Proofing::Base
:password,
:request_mode,
:request_timeout,
:org_id,
:api_key,
keyword_init: true,
allowed_members: [
:instant_verify_workflow,
Expand Down
28 changes: 28 additions & 0 deletions app/services/proofing/mock/ddp_mock_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Proofing
module Mock
class DdpMockClient < Proofing::Base
vendor_name 'DdpMock'

required_attributes :threatmetrix_session_id,
:state_id_number,
:first_name,
:last_name,
:dob,
:ssn,
:address1,
:city,
:state,
:zipcode

optional_attributes :address2, :phone, :email

stage :resolution

TRANSACTION_ID = 'ddp-mock-transaction-id-123'

proof do |applicant, result|
result.transaction_id = TRANSACTION_ID
end
end
end
end
10 changes: 4 additions & 6 deletions config/application.yml.default
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,12 @@ lexisnexis_trueid_liveness_nocropping_workflow: customers.gsa.trueid.workflow
lexisnexis_trueid_noliveness_cropping_workflow: customers.gsa.trueid.workflow
lexisnexis_trueid_noliveness_nocropping_workflow: customers.gsa.trueid.workflow
###################################################################
# LexisNexis ThreatMetrix ##########################################
# LexisNexis DDP/ThreatMetrix #####################################
lexisnexis_threatmetrix_api_key: test_api_key
lexisnexis_threatmetrix_base_url: https://www.example.com
lexisnexis_threatmetrix_request_mode: testing
lexisnexis_threatmetrix_org_id: test_account
lexisnexis_threatmetrix_username: test_username
lexisnexis_threatmetrix_password: test_password
lexisnexis_threatmetrix_instant_verify_timeout: 1.0
lexisnexis_threatmetrix_instant_verify_workflow: customers.gsa.instant.verify.workflow
lexisnexis_threatmetrix_timeout: 1.0
lexisnexis_threatmetrix_enabled: false
###################################################################
lockout_period_in_minutes: 10
log_to_stdout: false
Expand Down
8 changes: 3 additions & 5 deletions lib/identity_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,11 @@ def self.build_store(config_map)
config.add(:lexisnexis_trueid_noliveness_cropping_workflow, type: :string)
config.add(:lexisnexis_trueid_noliveness_nocropping_workflow, type: :string)
config.add(:lexisnexis_trueid_timeout, type: :float)
config.add(:lexisnexis_threatmetrix_api_key, type: :string)
config.add(:lexisnexis_threatmetrix_base_url, type: :string)
config.add(:lexisnexis_threatmetrix_request_mode, type: :string)
config.add(:lexisnexis_threatmetrix_enabled, type: :string)
config.add(:lexisnexis_threatmetrix_org_id, type: :string)
config.add(:lexisnexis_threatmetrix_username, type: :string)
config.add(:lexisnexis_threatmetrix_password, type: :string)
config.add(:lexisnexis_threatmetrix_instant_verify_timeout, type: :float)
config.add(:lexisnexis_threatmetrix_instant_verify_workflow, type: :string)
config.add(:lexisnexis_threatmetrix_timeout, type: :float)
config.add(:liveness_checking_enabled, type: :boolean)
config.add(:lockout_period_in_minutes, type: :integer)
config.add(:log_to_stdout, type: :boolean)
Expand Down
12 changes: 9 additions & 3 deletions spec/features/idv/doc_auth/verify_step_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,18 @@
allow(IdentityConfig.store).to receive(:aamva_supported_jurisdictions).and_return(
[Idp::Constants::MOCK_IDV_APPLICANT[:state_id_jurisdiction]],
)
user = create(:user, :signed_up)
expect_any_instance_of(Idv::Agent).
to receive(:proof_resolution).
with(
anything,
should_proof_state_id: true,
trace_id: anything,
threatmetrix_session_id: nil,
user_id: user.id,
).
and_call_original

user = create(:user, :signed_up)
sign_in_and_2fa_user(user)
complete_doc_auth_steps_before_verify_step
click_idv_continue
Expand All @@ -181,16 +183,18 @@
IdentityConfig.store.aamva_supported_jurisdictions -
[Idp::Constants::MOCK_IDV_APPLICANT[:state_id_jurisdiction]],
)
user = create(:user, :signed_up)
expect_any_instance_of(Idv::Agent).
to receive(:proof_resolution).
with(
anything,
should_proof_state_id: false,
trace_id: anything,
threatmetrix_session_id: nil,
user_id: user.id,
).
and_call_original

user = create(:user, :signed_up)
sign_in_and_2fa_user(user)
complete_doc_auth_steps_before_verify_step
click_idv_continue
Expand All @@ -203,17 +207,19 @@
it 'does not perform the state ID check' do
allow(IdentityConfig.store).to receive(:aamva_sp_banlist_issuers).
and_return('["urn:gov:gsa:openidconnect:sp:server"]')
user = create(:user, :signed_up)
expect_any_instance_of(Idv::Agent).
to receive(:proof_resolution).
with(
anything,
should_proof_state_id: false,
trace_id: anything,
threatmetrix_session_id: nil,
user_id: user.id,
).
and_call_original

visit_idp_from_sp_with_ial1(:oidc)
user = create(:user, :signed_up)
sign_in_and_2fa_user(user)
complete_doc_auth_steps_before_verify_step
click_idv_continue
Expand Down
5 changes: 5 additions & 0 deletions spec/fixtures/proofing/lexis_nexis/ddp/error_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"error_detail": "service_type",
"request_id":"1234-abcd",
"request_result":"fail_invalid_parameter"
}
Loading