Skip to content
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
87 changes: 87 additions & 0 deletions app/services/doc_auth/lexis_nexis/doc_pii_reader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module DocAuth
module LexisNexis
module DocPiiReader
PII_EXCLUDES = %w[
Age
DocSize
DOB_Day
DOB_Month
DOB_Year
ExpirationDate_Day
ExpirationDate_Month
ExpirationDate_Year
FullName
Portrait
Sex
].freeze

private

def read_pii(true_id_product)
return {} unless true_id_product&.dig(:IDAUTH_FIELD_DATA).present?
pii = {}
PII_INCLUDES.each do |true_id_key, idp_key|
pii[idp_key] = true_id_product[:IDAUTH_FIELD_DATA][true_id_key]
end
pii[:state_id_type] = DocAuth::Response::ID_TYPE_SLUGS[pii[:state_id_type]]

dob = parse_date(
year: pii.delete(:dob_year),
month: pii.delete(:dob_month),
day: pii.delete(:dob_day),
)
pii[:dob] = dob if dob

exp_date = parse_date(
year: pii.delete(:state_id_expiration_year),
month: pii.delete(:state_id_expiration_month),
day: pii.delete(:state_id_expiration_day),
)
pii[:state_id_expiration] = exp_date if exp_date

issued_date = parse_date(
year: pii.delete(:state_id_issued_year),
month: pii.delete(:state_id_issued_month),
day: pii.delete(:state_id_issued_day),
)
pii[:state_id_issued] = issued_date if issued_date

pii
end

PII_INCLUDES = {
'Fields_FirstName' => :first_name,
'Fields_MiddleName' => :middle_name,
'Fields_Surname' => :last_name,
'Fields_AddressLine1' => :address1,
'Fields_AddressLine2' => :address2,
'Fields_City' => :city,
'Fields_State' => :state,
'Fields_PostalCode' => :zipcode,
'Fields_DOB_Year' => :dob_year,
'Fields_DOB_Month' => :dob_month,
'Fields_DOB_Day' => :dob_day,
'Fields_DocumentNumber' => :state_id_number,
'Fields_IssuingStateCode' => :state_id_jurisdiction,
'Fields_xpirationDate_Day' => :state_id_expiration_day, # this is NOT a typo
'Fields_ExpirationDate_Month' => :state_id_expiration_month,
'Fields_ExpirationDate_Year' => :state_id_expiration_year,
'Fields_IssueDate_Day' => :state_id_issued_day,
'Fields_IssueDate_Month' => :state_id_issued_month,
'Fields_IssueDate_Year' => :state_id_issued_year,
'Fields_DocumentClassName' => :state_id_type,
'Fields_CountryCode' => :issuing_country_code,
}.freeze

def parse_date(year:, month:, day:)
Date.new(year.to_i, month.to_i, day.to_i).to_s if year.to_i.positive?
rescue ArgumentError
message = {
event: 'Failure to parse TrueID date',
}.to_json
Rails.logger.info(message)
nil
end
end
end
end
29 changes: 29 additions & 0 deletions app/services/doc_auth/lexis_nexis/image_metrics_reader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module DocAuth
module LexisNexis
module ImageMetricsReader
private

def read_image_metrics(true_id_product)
image_metrics = {}
return image_metrics unless true_id_product&.dig(:ParameterDetails).present?
true_id_product[:ParameterDetails].each do |detail|
next unless detail[:Group] == 'IMAGE_METRICS_RESULT'

inner_val = detail.dig(:Values).collect { |value| value.dig(:Value) }
image_metrics[detail[:Name]] = inner_val
end

transform_metrics(image_metrics)
end

def transform_metrics(img_metrics)
new_metrics = {}
img_metrics['Side']&.each_with_index do |side, i|
new_metrics[side.downcase.to_sym] = img_metrics.transform_values { |v| v[i] }
end

new_metrics
end
end
end
end
123 changes: 17 additions & 106 deletions app/services/doc_auth/lexis_nexis/responses/true_id_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,23 @@ module DocAuth
module LexisNexis
module Responses
class TrueIdResponse < DocAuth::Response
include ImageMetricsReader
include DocPiiReader
include ClassificationConcern
include SelfieConcern
PII_EXCLUDES = %w[
Age
DocSize
DOB_Day
DOB_Month
DOB_Year
ExpirationDate_Day
ExpirationDate_Month
ExpirationDate_Year
FullName
Portrait
Sex
].freeze

PII_INCLUDES = {
'Fields_FirstName' => :first_name,
'Fields_MiddleName' => :middle_name,
'Fields_Surname' => :last_name,
'Fields_AddressLine1' => :address1,
'Fields_AddressLine2' => :address2,
'Fields_City' => :city,
'Fields_State' => :state,
'Fields_PostalCode' => :zipcode,
'Fields_DOB_Year' => :dob_year,
'Fields_DOB_Month' => :dob_month,
'Fields_DOB_Day' => :dob_day,
'Fields_DocumentNumber' => :state_id_number,
'Fields_IssuingStateCode' => :state_id_jurisdiction,
'Fields_xpirationDate_Day' => :state_id_expiration_day, # this is NOT a typo
'Fields_ExpirationDate_Month' => :state_id_expiration_month,
'Fields_ExpirationDate_Year' => :state_id_expiration_year,
'Fields_IssueDate_Day' => :state_id_issued_day,
'Fields_IssueDate_Month' => :state_id_issued_month,
'Fields_IssueDate_Year' => :state_id_issued_year,
'Fields_DocumentClassName' => :state_id_type,
'Fields_CountryCode' => :issuing_country_code,
}.freeze

attr_reader :config, :http_response

def initialize(http_response, config, liveness_checking_enabled = false)
@config = config
@http_response = http_response
@liveness_checking_enabled = liveness_checking_enabled
@pii_from_doc = read_pii(true_id_product)
super(
success: successful_result?,
errors: error_messages,
extra: extra_attributes,
pii_from_doc: pii_from_doc,
pii_from_doc: @pii_from_doc,
)
rescue StandardError => e
NewRelic::Agent.notice_error(e)
Expand All @@ -72,7 +39,7 @@ def successful_result?
def error_messages
return {} if successful_result?

if true_id_product&.dig(:AUTHENTICATION_RESULT).present?
if with_authentication_result?
ErrorGenerator.new(config).generate_doc_auth_errors(response_info)
elsif true_id_product.present?
ErrorGenerator.wrapped_general_error(@liveness_checking_enabled)
Expand All @@ -82,7 +49,7 @@ def error_messages
end

def extra_attributes
if true_id_product&.dig(:AUTHENTICATION_RESULT).present?
if with_authentication_result?
attrs = response_info.merge(true_id_product[:AUTHENTICATION_RESULT])
attrs.reject! do |k, _v|
PII_EXCLUDES.include?(k) || k.start_with?('Alert_')
Expand All @@ -98,38 +65,6 @@ def extra_attributes
basic_logging_info.merge(attrs)
end

def pii_from_doc
return {} unless true_id_product&.dig(:IDAUTH_FIELD_DATA).present?
pii = {}
PII_INCLUDES.each do |true_id_key, idp_key|
pii[idp_key] = true_id_product[:IDAUTH_FIELD_DATA][true_id_key]
end
pii[:state_id_type] = DocAuth::Response::ID_TYPE_SLUGS[pii[:state_id_type]]

dob = parse_date(
year: pii.delete(:dob_year),
month: pii.delete(:dob_month),
day: pii.delete(:dob_day),
)
pii[:dob] = dob if dob

exp_date = parse_date(
year: pii.delete(:state_id_expiration_year),
month: pii.delete(:state_id_expiration_month),
day: pii.delete(:state_id_expiration_day),
)
pii[:state_id_expiration] = exp_date if exp_date

issued_date = parse_date(
year: pii.delete(:state_id_issued_year),
month: pii.delete(:state_id_issued_month),
day: pii.delete(:state_id_issued_day),
)
pii[:state_id_issued] = issued_date if issued_date

pii
end

def attention_with_barcode?
return false unless doc_auth_result_attention?

Expand All @@ -143,10 +78,14 @@ def billed?
end

def doc_auth_success?
transaction_status_passed? &&
# really it's everything else excluding selfie
((transaction_status_passed? &&
true_id_product.present? &&
product_status_passed? &&
doc_auth_result_passed?
) ||
attention_with_barcode?
) && id_type_supported?
end

# @return [:success, :fail, :not_processed]
Expand Down Expand Up @@ -230,7 +169,7 @@ def create_response_info
alert_failure_count: alerts[:failed]&.count.to_i,
log_alert_results: log_alert_formatter.log_alerts(alerts),
portrait_match_results: portrait_match_results,
image_metrics: parse_image_metrics,
image_metrics: read_image_metrics(true_id_product),
address_line2_present: !pii_from_doc[:address2].blank?,
classification_info: classification_info,
liveness_enabled: @liveness_checking_enabled,
Expand All @@ -255,7 +194,7 @@ def all_passed?
end

def selfie_result
response_info&.dig(:portrait_match_results, :FaceMatchResult)
portrait_match_results&.dig(:FaceMatchResult)
end

def product_status_passed?
Expand Down Expand Up @@ -324,7 +263,7 @@ def parsed_alerts
return @new_alerts if defined?(@new_alerts)

@new_alerts = { passed: [], failed: [] }
return @new_alerts unless true_id_product&.dig(:AUTHENTICATION_RESULT).present?
return @new_alerts unless with_authentication_result?
all_alerts = true_id_product[:AUTHENTICATION_RESULT].select do |key|
key.start_with?('Alert_')
end
Expand Down Expand Up @@ -363,28 +302,6 @@ def combine_alert_data(all_alerts, alert_name, region_details)
new_alert_data
end

def parse_image_metrics
image_metrics = {}
return image_metrics unless true_id_product&.dig(:ParameterDetails).present?
true_id_product[:ParameterDetails].each do |detail|
next unless detail[:Group] == 'IMAGE_METRICS_RESULT'

inner_val = detail.dig(:Values).collect { |value| value.dig(:Value) }
image_metrics[detail[:Name]] = inner_val
end

transform_metrics(image_metrics)
end

def transform_metrics(img_metrics)
new_metrics = {}
img_metrics['Side']&.each_with_index do |side, i|
new_metrics[side.downcase.to_sym] = img_metrics.transform_values { |v| v[i] }
end

new_metrics
end

# Generate a hash for image references information that can be linked to Alert
# @return A hash with region_id => {:key : 'What region', :side: 'Front|Back'}
def parse_document_region
Expand Down Expand Up @@ -432,14 +349,8 @@ def transform_document_region(region_details, image_sides)
end
end

def parse_date(year:, month:, day:)
Date.new(year.to_i, month.to_i, day.to_i).to_s if year.to_i.positive?
rescue ArgumentError
message = {
event: 'Failure to parse TrueID date',
}.to_json
Rails.logger.info(message)
nil
def with_authentication_result?
true_id_product&.dig(:AUTHENTICATION_RESULT).present?
end
end
end
Expand Down
5 changes: 4 additions & 1 deletion app/services/doc_auth/mock/result_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,10 @@ def self.create_network_error_response
end

def doc_auth_success?
doc_auth_result_from_uploaded_file == 'Passed' || errors.blank?
(doc_auth_result_from_uploaded_file == 'Passed' ||
errors.blank? ||
attention_with_barcode?
) && id_type_supported?
end

def selfie_status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ def get_decision_product(resp)
end

it 'produces reasonable output for a malformed TrueID response' do
allow(NewRelic::Agent).to receive(:notice_error)
output = described_class.new(failure_response_malformed, config).to_h

expect(output[:success]).to eq(false)
Expand Down Expand Up @@ -654,6 +655,13 @@ def get_decision_product(resp)
expect(response.doc_auth_success?).to eq(false)
end
end

context 'when attention barcode read' do
let(:response) { described_class.new(attention_barcode_read, config) }
it 'returns true' do
expect(response.doc_auth_success?).to eq(true)
end
end
end

describe '#selfie_status' do
Expand Down