Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0183bdd
Refactor in preparation for allowing more errors
charleyf Jan 18, 2024
b36be98
Draft in error reporting
charleyf Jan 18, 2024
02a46a8
Fill in selfie error generator
charleyf Jan 18, 2024
dada67c
Add comments
charleyf Jan 18, 2024
1396c89
Add comments to mark broken tests
charleyf Jan 18, 2024
165a36d
Fix comment
charleyf Jan 18, 2024
d2dcb1e
Fix mixed up error names
charleyf Jan 18, 2024
ee025ce
Use a case (switch) statement instead of ifs
charleyf Jan 19, 2024
5042766
Revise comments
charleyf Jan 19, 2024
cea16b8
Add comment to mark message
charleyf Jan 19, 2024
313f1d1
Merge branch 'main' into charley/lg-12157-send-selfie-fail-info-to-fe
charleyf Jan 19, 2024
23ff4d9
Fix missing `end`
charleyf Jan 22, 2024
20934da
Add missing error keys
charleyf Jan 22, 2024
69a2ced
User Facing Improvements, In-Person Proofing, add error messages behi…
charleyf Jan 22, 2024
005821d
changelog: User Facing Improvements, In-Person Proofing, add error me…
charleyf Jan 22, 2024
e061f6c
Draft in selfie error check fields
charleyf Jan 22, 2024
08a2bdc
Update the two selfie error booleans
charleyf Jan 22, 2024
bd061f4
Add tests for the concern
charleyf Jan 22, 2024
815802d
Re-add accidentally deleted return value
charleyf Jan 22, 2024
d153145
Fix upload presenter tests
charleyf Jan 22, 2024
6a3ef4f
Get tests passing
charleyf Jan 23, 2024
343a2db
Fix string for success error message
charleyf Jan 23, 2024
05f274f
Update presenter to only send selfie fail info if it's relevant
charleyf Jan 23, 2024
4b7e8cd
Merge branch 'main' into charley/lg-12157-send-selfie-fail-info-to-fe
charleyf Jan 23, 2024
f3dc0b2
Fix test with new response params
charleyf Jan 23, 2024
eeca656
Fix selfie concern tests
charleyf Jan 23, 2024
0870619
Fix acuant api tests
charleyf Jan 23, 2024
453cdc4
Fix tests
charleyf Jan 23, 2024
b86e5f8
Centralize facematcherror text comparisons into the concern
charleyf Jan 23, 2024
a563e82
Fix testing problem
charleyf Jan 23, 2024
30aab53
Use `blank` instead of `empty`
charleyf Jan 24, 2024
94aa1d6
Fix two bugs that compounded each other
charleyf Jan 24, 2024
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
1 change: 1 addition & 0 deletions app/forms/idv/api_image_upload_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def extra_attributes

@extra_attributes[:front_image_fingerprint] = front_image_fingerprint
@extra_attributes[:back_image_fingerprint] = back_image_fingerprint
@extra_attributes[:liveness_checking_required] = liveness_checking_required
@extra_attributes
end

Expand Down
14 changes: 14 additions & 0 deletions app/presenters/image_upload_response_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ def as_json(*)
json[:ocr_pii] = ocr_pii
json[:result_failed] = doc_auth_result_failed?
json[:doc_type_supported] = doc_type_supported?
json[:selfie_live] = selfie_live? if show_selfie_failures
json[:selfie_quality_good] = selfie_quality_good? if show_selfie_failures
json[:failed_image_fingerprints] = failed_fingerprints
json
end
Expand Down Expand Up @@ -89,4 +91,16 @@ def doc_type_supported?
def failed_fingerprints
@form_response.extra[:failed_image_fingerprints] || { front: [], back: [] }
end

def show_selfie_failures
@form_response.extra[:liveness_checking_required] == true
end

def selfie_live?
@form_response.respond_to?(:selfie_live?) ? @form_response.selfie_live? : true
end

def selfie_quality_good?
@form_response.respond_to?(:selfie_quality_good?) ? @form_response.selfie_quality_good? : true
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module Acuant
module Responses
class GetResultsResponse < DocAuth::Response
include ClassificationConcern
include SelfieConcern
attr_reader :config

BARCODE_COULD_NOT_BE_READ_ERROR = '2D Barcode Read'.freeze
Expand Down Expand Up @@ -144,6 +145,11 @@ def passed_result?
result_code == DocAuth::Acuant::ResultCodes::PASSED
end

# This is not implemented for the acuant call since that is currently not in use
def portrait_match_results
return {}
end

def get_image_side_name(side_number)
side_number == 0 ? :front : :back
end
Expand Down
45 changes: 42 additions & 3 deletions app/services/doc_auth/error_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module DocAuth
class ErrorGenerator
include SelfieConcern
attr_reader :config

def initialize(config)
Expand Down Expand Up @@ -58,9 +59,15 @@ def generate_doc_auth_errors(response_info)
unknown_fail_count = scan_for_unknown_alerts(response_info)
alert_error_count -= unknown_fail_count

# If we have document type errors (Ex: passport was uploaded) return only
# document type errors for both the "FRONT" and "BACK" fields (but not "SELFIE")
# this return will never include any selfie errors at the moment.
doc_type_errors = get_id_type_errors(response_info[:classification_info])
return doc_type_errors.to_h unless doc_type_errors.nil? || doc_type_errors.empty?

# If we have image metric errors (Ex: DPI too low) return only
# image metric errors for both the "FRONT" and "BACK" fields (but not "SELFIE")
# this return will never include any selfie errors at the moment.
image_metric_errors = get_image_metric_errors(response_info[:image_metrics])
return image_metric_errors.to_h unless image_metric_errors.empty?

Expand All @@ -71,6 +78,9 @@ def generate_doc_auth_errors(response_info)
error = ''
side = nil

# If we don't have document type or image metric errors then sort out which
# errors to return. Note that there's a :general error added in the
# `to_h` method of error_result
if alert_error_count < 1
config.warn_notifier&.call(
message: 'DocAuth failure escaped without useful errors',
Expand Down Expand Up @@ -189,14 +199,43 @@ def get_error_messages(liveness_enabled, response_info)
end
end

portrait_match_results = response_info[:portrait_match_results] || {}
if liveness_enabled && portrait_match_results.dig(:FaceMatchResult) != 'Pass'
errors[SELFIE] << Errors::SELFIE_FAILURE
selfie_error = get_selfie_error(liveness_enabled, response_info)
if liveness_enabled && !!selfie_error
errors[SELFIE] << selfie_error
end

errors
end

def get_selfie_error(liveness_enabled, response_info)
# The part of the response that contains information about the selfie
portrait_match_results = response_info[:portrait_match_results] || {}
# The overall result of the selfie, 'Pass' or 'Fail'
face_match_result = portrait_match_results.dig(:FaceMatchResult)
# The reason for failure (if it failed), also sometimes contains success info
face_match_error = portrait_match_results.dig(:FaceErrorMessage)

# No error if liveness is not enabled or if there's no failure
if !liveness_enabled || !face_match_result || face_match_result == 'Pass'
return nil
end

# Error when the image on the id does not match the selfie image, but the image was acceptable
if error_is_success(face_match_error)
return Errors::SELFIE_FAILURE
end
# Error when the image on the id is poor quality
if error_is_poor_quality(face_match_error)
return Errors::SELFIE_POOR_QUALITY
end
# Error when the image on the id is not live
if error_is_not_live(face_match_error)
return Errors::SELFIE_NOT_LIVE
end
# Fallback, we don't expect this to happen
return Errors::SELFIE_FAILURE
end

def scan_for_unknown_alerts(response_info)
all_alerts = [
*response_info[:processed_alerts][:failed],
Expand Down
5 changes: 5 additions & 0 deletions app/services/doc_auth/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ module Errors
MULTIPLE_FRONT_ID_FAILURES = 'multiple_front_id_failures'
REF_CONTROL_NUMBER_CHECK = 'ref_control_number_check'
SELFIE_FAILURE = 'selfie_failure'
SELFIE_NOT_LIVE = 'selfie_not_live'
SELFIE_POOR_QUALITY = 'selfie_poor_quality'
SEX_CHECK = 'sex_check'
VISIBLE_COLOR_CHECK = 'visible_color_check'
VISIBLE_PHOTO_CHECK = 'visible_photo_check'
Expand Down Expand Up @@ -115,8 +117,11 @@ module Errors
MULTIPLE_FRONT_ID_FAILURES => { long_msg: MULTIPLE_FRONT_ID_FAILURES, field_msg: FALLBACK_FIELD_LEVEL, hints: true },
MULTIPLE_BACK_ID_FAILURES => { long_msg: MULTIPLE_BACK_ID_FAILURES, field_msg: FALLBACK_FIELD_LEVEL, hints: true },
GENERAL_ERROR => { long_msg: GENERAL_ERROR, field_msg: FALLBACK_FIELD_LEVEL, hints: true },
# TODO, theses messages need modifying
# Liveness, use general error for now
SELFIE_FAILURE => { long_msg: GENERAL_ERROR, field_msg: FALLBACK_FIELD_LEVEL, hints: false },
SELFIE_NOT_LIVE => { long_msg: GENERAL_ERROR, field_msg: FALLBACK_FIELD_LEVEL, hints: false },
SELFIE_POOR_QUALITY => { long_msg: GENERAL_ERROR, field_msg: FALLBACK_FIELD_LEVEL, hints: false },
}
# rubocop:enable Layout/LineLength
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module LexisNexis
module Responses
class TrueIdResponse < DocAuth::Response
include ClassificationConcern
include SelfieConcern
PII_EXCLUDES = %w[
Age
DocSize
Expand Down Expand Up @@ -226,7 +227,7 @@ def create_response_info
processed_alerts: alerts,
alert_failure_count: alerts[:failed]&.count.to_i,
log_alert_results: log_alert_formatter.log_alerts(alerts),
portrait_match_results: true_id_product&.dig(:PORTRAIT_MATCH_RESULT),
portrait_match_results: portrait_match_results,
image_metrics: parse_image_metrics,
address_line2_present: !pii_from_doc[:address2].blank?,
classification_info: classification_info,
Expand Down Expand Up @@ -292,6 +293,10 @@ def classification_info
}
end

def portrait_match_results
true_id_product&.dig(:PORTRAIT_MATCH_RESULT)
end

def doc_auth_result
true_id_product&.dig(:AUTHENTICATION_RESULT, :DocAuthResult)
end
Expand Down
3 changes: 3 additions & 0 deletions app/services/doc_auth/mock/result_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module DocAuth
module Mock
class ResultResponse < DocAuth::Response
include DocAuth::ClassificationConcern
include DocAuth::SelfieConcern
include DocAuth::Mock::YmlLoaderConcern

attr_reader :uploaded_file, :config
Expand All @@ -16,6 +17,8 @@ def initialize(uploaded_file, selfie_check_performed, config)
pii_from_doc: pii_from_doc,
doc_type_supported: id_type_supported?,
selfie_check_performed: selfie_check_performed,
selfie_live: selfie_live?,
selfie_quality_good: selfie_quality_good?,
extra: {
doc_auth_result: doc_auth_result,
portrait_match_results: portrait_match_results,
Expand Down
21 changes: 19 additions & 2 deletions app/services/doc_auth/response.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module DocAuth
class Response
attr_reader :errors, :exception, :extra, :pii_from_doc, :doc_type_supported
attr_reader :errors, :exception, :extra, :pii_from_doc, :doc_type_supported, :selfie_live,
:selfie_quality_good

ID_TYPE_SLUGS = {
'Identification Card' => 'state_id_card',
Expand All @@ -15,7 +16,9 @@ def initialize(
pii_from_doc: {},
attention_with_barcode: false,
doc_type_supported: true,
selfie_check_performed: false
selfie_check_performed: false,
selfie_live: true,
selfie_quality_good: true
)
@success = success
@errors = errors.to_h
Expand All @@ -25,6 +28,8 @@ def initialize(
@attention_with_barcode = attention_with_barcode
@doc_type_supported = doc_type_supported
@selfie_check_performed = selfie_check_performed
@selfie_live = selfie_live
@selfie_quality_good = selfie_quality_good
end

def merge(other)
Expand All @@ -36,6 +41,8 @@ def merge(other)
pii_from_doc: pii_from_doc.merge(other.pii_from_doc),
attention_with_barcode: attention_with_barcode? || other.attention_with_barcode?,
doc_type_supported: doc_type_supported? || other.doc_type_supported?,
selfie_live: selfie_live?,
selfie_quality_good: selfie_quality_good?,
)
end

Expand All @@ -47,6 +54,14 @@ def doc_type_supported?
@doc_type_supported
end

def selfie_live?
@selfie_live
end

def selfie_quality_good?
@selfie_quality_good
end

# We use `#to_h` to serialize this for logging. Make certain not to include
# the `#pii` value here.
def to_h
Expand All @@ -56,6 +71,8 @@ def to_h
exception: exception,
attention_with_barcode: attention_with_barcode?,
doc_type_supported: doc_type_supported?,
selfie_live: selfie_live?,
selfie_quality_good: selfie_quality_good?,
doc_auth_success: doc_auth_success?,
selfie_success: selfie_success,
}.merge(extra)
Expand Down
41 changes: 41 additions & 0 deletions app/services/doc_auth/selfie_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module DocAuth
module SelfieConcern
extend ActiveSupport::Concern
def selfie_live?
portait_error = get_portrait_error(portrait_match_results)
return true if portait_error.nil? || portait_error.blank?
return error_is_not_live(portait_error)
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.

Nit, redundant return, and there are few methods down there too.

end

def selfie_quality_good?
portait_error = get_portrait_error(portrait_match_results)
return true if portait_error.nil? || portait_error.blank?
return error_is_poor_quality(portait_error)
end

def error_is_success(error_message)
return error_message != ERROR_TEXTS[:success]
end

def error_is_not_live(error_message)
return error_message != ERROR_TEXTS[:not_live]
end

def error_is_poor_quality(error_message)
return error_message != ERROR_TEXTS[:poor_quality]
end

private

ERROR_TEXTS = {
success: 'Successful. Liveness: Live',
not_live: 'Liveness: NotLive',
poor_quality: 'Liveness: PoorQuality',
}

# @param [Object] portrait_match_results trueid portait match info
def get_portrait_error(portrait_match_results)
portrait_match_results&.with_indifferent_access&.dig(:FaceErrorMessage)
end
end
end
Loading