diff --git a/Gemfile b/Gemfile index 9fa540d..6387921 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gemspec gem 'aamva', github: '18F/identity-aamva-api-client-gem', tag: 'v3.4.1' gem 'identity-doc-auth', github: '18F/identity-doc-auth', tag: 'v0.3.1' -gem 'lexisnexis', github: '18F/identity-lexisnexis-api-client-gem', tag: 'v2.4.1' +gem 'lexisnexis', github: '18F/identity-lexisnexis-api-client-gem', tag: 'v2.5.0' gem 'proofer', github: '18F/identity-proofer-gem', tag: 'v2.7.1' group :test do diff --git a/lib/identity-idp-functions/version.rb b/lib/identity-idp-functions/version.rb index 1bfb5f5..fb23b9e 100644 --- a/lib/identity-idp-functions/version.rb +++ b/lib/identity-idp-functions/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module IdentityIdpFunctions - VERSION = '0.7.8' + VERSION = '0.8.0' end diff --git a/source/proof_address/lib/Gemfile b/source/proof_address/lib/Gemfile index 6818fdb..8d2edb5 100644 --- a/source/proof_address/lib/Gemfile +++ b/source/proof_address/lib/Gemfile @@ -4,6 +4,6 @@ ruby '~> 2.7.0' gem 'aws-sdk-ssm', '~> 1.55' gem 'faraday' -gem 'lexisnexis', github: '18F/identity-lexisnexis-api-client-gem', tag: 'v2.4.1' +gem 'lexisnexis', github: '18F/identity-lexisnexis-api-client-gem', tag: 'v2.5.0' gem 'proofer', github: '18F/identity-proofer-gem', tag: 'v2.7.1' gem 'retries' diff --git a/source/proof_resolution/lib/Gemfile b/source/proof_resolution/lib/Gemfile index 69b5361..1a78cb5 100644 --- a/source/proof_resolution/lib/Gemfile +++ b/source/proof_resolution/lib/Gemfile @@ -5,6 +5,6 @@ ruby '~> 2.7.0' gem 'aamva', github: '18F/identity-aamva-api-client-gem', tag: 'v3.4.1' gem 'aws-sdk-ssm', '~> 1.55' gem 'faraday' -gem 'lexisnexis', github: '18F/identity-lexisnexis-api-client-gem', tag: 'v2.4.1' +gem 'lexisnexis', github: '18F/identity-lexisnexis-api-client-gem', tag: 'v2.5.0' gem 'proofer', github: '18F/identity-proofer-gem', tag: 'v2.7.1' gem 'retries' diff --git a/source/proof_resolution/lib/proof_resolution.rb b/source/proof_resolution/lib/proof_resolution.rb index 83a8629..c520b2e 100644 --- a/source/proof_resolution/lib/proof_resolution.rb +++ b/source/proof_resolution/lib/proof_resolution.rb @@ -16,21 +16,75 @@ def self.handle(event:, context:, &callback_block) # rubocop:disable Lint/Unused new(**params).proof(&callback_block) end - attr_reader :applicant_pii, :callback_url, :should_proof_state_id, :trace_id, :timer - - def initialize(applicant_pii:, callback_url:, should_proof_state_id:, trace_id: nil) + attr_reader :applicant_pii, + :callback_url, + :trace_id, + :timer + + def initialize( + applicant_pii:, + callback_url:, + should_proof_state_id:, + dob_year_only: false, + trace_id: nil + ) @applicant_pii = applicant_pii @callback_url = callback_url @should_proof_state_id = should_proof_state_id + @dob_year_only = dob_year_only @trace_id = trace_id @timer = IdentityIdpFunctions::Timer.new end + def should_proof_state_id? + @should_proof_state_id + end + + def dob_year_only? + @dob_year_only + end + + CallbackLogData = Struct.new( + :result, + :resolution_success, + :state_id_success, + keyword_init: true, + ) + + # rubocop:disable Metrics/PerceivedComplexity def proof set_up_env! raise Errors::MisconfiguredLambdaError if !block_given? && api_auth_token.to_s.empty? + callback_log_data = if dob_year_only? && should_proof_state_id? + proof_aamva_then_lexisnexis_dob_only + else + proof_lexisnexis_then_aamva + end + + callback_body = { + resolution_result: callback_log_data.result, + } + + if block_given? + yield callback_body + else + post_callback(callback_body: callback_body) + end + ensure + log_event( + 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 + # rubocop:enable Metrics/PerceivedComplexity + + # @return [CallbackLogData] + def proof_lexisnexis_then_aamva proofer_result = timer.time('resolution') do with_retries(**faraday_retry_options) do lexisnexis_proofer.proof(applicant_pii) @@ -47,29 +101,57 @@ def proof result[:exception] = proofer_result.exception.inspect if proofer_result.exception state_id_success = nil - if should_proof_state_id && result[:success] + if should_proof_state_id? && result[:success] timer.time('state_id') do proof_state_id(result) end state_id_success = result[:success] end - callback_body = { - resolution_result: result, - } + CallbackLogData.new( + result: result, + resolution_success: resolution_success, + state_id_success: state_id_success, + ) + end - if block_given? - yield callback_body - else - post_callback(callback_body: callback_body) + # @return [CallbackLogData] + def proof_aamva_then_lexisnexis_dob_only + proofer_result = timer.time('state_id') do + with_retries(**faraday_retry_options) do + aamva_proofer.proof(applicant_pii) + end end - ensure - log_event( - name: 'ProofResolution', - trace_id: trace_id, + + result = proofer_result.to_h + state_id_success = proofer_result.success? + resolution_success = nil + + result[:context] = { stages: [{ state_id: Aamva::Proofer.vendor_name }] } + + if state_id_success + lexisnexis_result = timer.time('resolution') do + with_retries(**faraday_retry_options) do + lexisnexis_proofer.proof(applicant_pii.merge(dob_year_only: dob_year_only?)) + end + end + + resolution_success = lexisnexis_result.success? + + result.merge(lexisnexis_result.to_h) do |key, orig, current| + key == :messages ? orig + current : current + end + + result[:context][:stages].push(resolution: LexisNexis::InstantVerify::Proofer.vendor_name) + result[:transaction_id] = lexisnexis_result.transaction_id + result[:timed_out] = lexisnexis_result.timed_out? + result[:exception] = lexisnexis_result.exception.inspect if lexisnexis_result.exception + end + + CallbackLogData.new( + result: result, resolution_success: resolution_success, state_id_success: state_id_success, - timing: timer.results, ) end diff --git a/source/proof_resolution/spec/proof_resolution_spec.rb b/source/proof_resolution/spec/proof_resolution_spec.rb index 0245073..adc5a41 100644 --- a/source/proof_resolution/spec/proof_resolution_spec.rb +++ b/source/proof_resolution/spec/proof_resolution_spec.rb @@ -123,6 +123,7 @@ let(:should_proof_state_id) { true } let(:lexisnexis_proofer) { instance_double(LexisNexis::InstantVerify::Proofer) } let(:aamva_proofer) { instance_double(Aamva::Proofer) } + let(:dob_year_only) { false } subject(:function) do IdentityIdpFunctions::ProofResolution.new( @@ -130,6 +131,7 @@ applicant_pii: applicant_pii, should_proof_state_id: should_proof_state_id, trace_id: trace_id, + dob_year_only: dob_year_only, ) end @@ -204,6 +206,47 @@ end end + context 'checking DOB year only' do + let(:dob_year_only) { true } + + it 'only sends the birth year to LexisNexis (extra applicant attribute)' do + expect(aamva_proofer).to receive(:proof).and_return(Proofer::Result.new) + expect(lexisnexis_proofer).to receive(:proof). + with(hash_including(dob_year_only: true)). + and_return(Proofer::Result.new) + + function.proof + end + + it 'does not check LexisNexis when AAMVA proofing does not match' do + expect(aamva_proofer).to receive(:proof).and_return(Proofer::Result.new(exception: 'error')) + expect(lexisnexis_proofer).to_not receive(:proof) + + function.proof + end + + it 'logs the correct context' do + transaction_id = SecureRandom.uuid + + expect(aamva_proofer).to receive(:proof).and_return(Proofer::Result.new) + expect(lexisnexis_proofer).to receive(:proof). + and_return(Proofer::Result.new(transaction_id: transaction_id)) + + function.proof + + expect(WebMock).to(have_requested(:post, callback_url).with do |request| + body = JSON.parse(request.body, symbolize_names: true) + + expect(body.dig(:resolution_result, :context, :stages)).to eq [ + { state_id: 'aamva:state_id' }, + { resolution: 'lexisnexis:instant_verify' }, + ] + + expect(body.dig(:resolution_result, :transaction_id)).to eq(transaction_id) + end) + end + end + context 'when IDP auth token is blank' do it_behaves_like 'misconfigured proofer' end diff --git a/source/proof_resolution_mock/lib/proof_resolution_mock.rb b/source/proof_resolution_mock/lib/proof_resolution_mock.rb index 8ea74d8..5a63511 100644 --- a/source/proof_resolution_mock/lib/proof_resolution_mock.rb +++ b/source/proof_resolution_mock/lib/proof_resolution_mock.rb @@ -16,16 +16,31 @@ def self.handle(event:, context:, &callback_block) # rubocop:disable Lint/Unused new(**params).proof(&callback_block) end - attr_reader :applicant_pii, :callback_url, :should_proof_state_id, :trace_id, :timer - - def initialize(applicant_pii:, callback_url:, should_proof_state_id:, trace_id: nil) + attr_reader :applicant_pii, :callback_url, :trace_id, :timer + + def initialize( + applicant_pii:, + callback_url:, + should_proof_state_id:, + dob_year_only: false, + trace_id: nil + ) @applicant_pii = applicant_pii @callback_url = callback_url @should_proof_state_id = should_proof_state_id + @dob_year_only = dob_year_only @trace_id = trace_id @timer = IdentityIdpFunctions::Timer.new end + def should_proof_state_id? + @should_proof_state_id + end + + def dob_year_only? + @dob_year_only + end + def proof raise Errors::MisconfiguredLambdaError if !block_given? && api_auth_token.to_s.empty? @@ -46,7 +61,7 @@ def proof result[:exception] = proofer_result.exception.inspect if proofer_result.exception state_id_success = nil - if should_proof_state_id && result[:success] + if should_proof_state_id? && result[:success] timer.time('state_id') do proof_state_id(result) end diff --git a/source/proof_resolution_mock/spec/proof_resolution_mock_spec.rb b/source/proof_resolution_mock/spec/proof_resolution_mock_spec.rb index 88d059b..2f730b0 100644 --- a/source/proof_resolution_mock/spec/proof_resolution_mock_spec.rb +++ b/source/proof_resolution_mock/spec/proof_resolution_mock_spec.rb @@ -98,12 +98,14 @@ describe '#proof' do let(:should_proof_state_id) { true } + let(:dob_year_only) { false } subject(:function) do IdentityIdpFunctions::ProofResolutionMock.new( callback_url: callback_url, applicant_pii: applicant_pii, should_proof_state_id: should_proof_state_id, + dob_year_only: dob_year_only, trace_id: trace_id, ) end