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
2 changes: 2 additions & 0 deletions app/controllers/concerns/idv/verify_info_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,12 @@ def async_state_done(current_async_state)
previous_ssn_edit_distance: previous_ssn_edit_distance,
pii_like_keypaths: [
[:errors, :ssn],
[:errors, :state_id_jurisdiction],
[:proofing_results, :context, :stages, :resolution, :errors, :ssn],
[:proofing_results, :context, :stages, :residential_address, :errors, :ssn],
[:proofing_results, :context, :stages, :threatmetrix, :response_body, :first_name],
[:proofing_results, :context, :stages, :state_id, :state_id_jurisdiction],
[:proofing_results, :context, :stages, :state_id, :errors, :state_id_jurisdiction],
[:proofing_results, :biographical_info, :identity_doc_address_state],
[:proofing_results, :biographical_info, :state_id_jurisdiction],
[:proofing_results, :biographical_info],
Expand Down
14 changes: 13 additions & 1 deletion app/controllers/idv/address_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ def self.step_info

def build_address_form
Idv::AddressForm.new(
idv_session.updated_user_address || address_from_document,
idv_session.updated_user_address || address_from_document || null_address,
)
end

def address_from_document
return if idv_session.pii_from_doc.state_id_type == 'passport'

Pii::Address.new(
address1: idv_session.pii_from_doc.address1,
address2: idv_session.pii_from_doc.address2,
Expand All @@ -56,6 +58,16 @@ def address_from_document
)
end

def null_address
Pii::Address.new(
address1: nil,
address2: nil,
city: nil,
state: nil,
zipcode: nil,
)
end

def success
idv_session.address_edited = address_edited?
idv_session.updated_user_address = @address_form.updated_user_address
Expand Down
3 changes: 3 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1969,6 +1969,7 @@ def idv_doc_auth_submitted_image_upload_form(
# @option extra [Boolean] 'OrientationChanged'
# @option extra [Boolean] 'PresentationChanged'
# @param ["Passport","DriversLicense"] document_type Document capture user flow
# @param [Hash] passport_check_result The results of the Dos API call
# The document capture image was uploaded to vendor during the IDV process
Comment thread
jmax-gsa marked this conversation as resolved.
def idv_doc_auth_submitted_image_upload_vendor(
success:,
Expand Down Expand Up @@ -2018,6 +2019,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
acuant_sdk_upgrade_ab_test_bucket: nil,
liveness_enabled: nil,
document_type: nil,
passport_check_result: nil,
**extra
)
track_event(
Expand Down Expand Up @@ -2069,6 +2071,7 @@ def idv_doc_auth_submitted_image_upload_vendor(
acuant_sdk_upgrade_ab_test_bucket:,
liveness_enabled:,
document_type:,
passport_check_result:,
**extra,
)
end
Expand Down
49 changes: 36 additions & 13 deletions app/services/doc_auth/mock/result_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ def initialize(uploaded_file, config, selfie_required = false)
@selfie_required = selfie_required
super(
success: success?,
errors: errors,
pii_from_doc: pii_from_doc,
errors:,
pii_from_doc:,
doc_type_supported: id_type_supported?,
selfie_live: selfie_live?,
selfie_quality_good: selfie_quality_good?,
selfie_status: selfie_status,
extra: {
doc_auth_result: doc_auth_result,
portrait_match_results: portrait_match_results,
doc_auth_result:,
passport_check_result:,
portrait_match_results:,
billed: true,
classification_info: classification_info,
classification_info:,
workflow: workflow,
liveness_checking_required: @selfie_required,
**@response_info.to_h,
Expand All @@ -46,6 +47,7 @@ def errors
passed = file_data.dig('passed_alerts')
face_match_result = file_data.dig('portrait_match_results', 'FaceMatchResult')
classification_info = file_data.dig('classification_info')&.symbolize_keys
passport_check_result = file_data.dig('passport_check_result', 'PassportCheckResult')
# Pass and doc type is ok
has_fields = [
doc_auth_result,
Expand All @@ -54,6 +56,7 @@ def errors
passed,
face_match_result,
classification_info,
passport_check_result,
].any?(&:present?)

if has_fields
Expand All @@ -70,6 +73,10 @@ def errors
mock_args[:passed] = passed.map!(&:symbolize_keys) if passed.present?
mock_args[:liveness_enabled] = face_match_result ? true : false
mock_args[:classification_info] = classification_info if classification_info.present?
if passport_check_result.present?
mock_args[:passport_check_result] =
classification_info
end
@response_info = create_response_info(**mock_args)
ErrorGenerator.new(config).generate_doc_auth_errors(@response_info)
elsif file_data.include?(:general) # general is the key for errors from parsing
Expand Down Expand Up @@ -132,7 +139,15 @@ def parsed_alerts
end

def parsed_pii_from_doc
if parsed_data_from_uploaded_file.has_key?('document')
return if !parsed_data_from_uploaded_file.has_key?('document')

if parsed_data_from_uploaded_file['document']['state_id_type'] == 'passport'
Pii::Passport.new(
**Idp::Constants::MOCK_IDV_APPLICANT.merge(
parsed_data_from_uploaded_file['document'].symbolize_keys,
).slice(*Pii::Passport.members),
)
else
Pii::StateId.new(
**Idp::Constants::MOCK_IDV_APPLICANT.merge(
parsed_data_from_uploaded_file['document'].symbolize_keys,
Expand Down Expand Up @@ -161,6 +176,12 @@ def portrait_match_results
&.deep_symbolize_keys
end

def passport_check_result
parsed_data_from_uploaded_file.dig('passport_check_result')
&.transform_keys! { |key| key.to_s.camelize }
&.deep_symbolize_keys
end

def classification_info
info = parsed_data_from_uploaded_file&.[]('classification_info') || {}
info.to_h.symbolize_keys
Expand Down Expand Up @@ -214,13 +235,14 @@ def parse_uri
}.freeze

def create_response_info(
doc_auth_result: 'Failed',
passed: [],
failed: DEFAULT_FAILED_ALERTS,
liveness_enabled: false,
image_metrics: DEFAULT_IMAGE_METRICS,
classification_info: nil
)
doc_auth_result: 'Failed',
passed: [],
failed: DEFAULT_FAILED_ALERTS,
liveness_enabled: false,
image_metrics: DEFAULT_IMAGE_METRICS,
classification_info: nil,
passport_check_result: nil
)
merged_image_metrics = DEFAULT_IMAGE_METRICS.deep_merge(image_metrics)
{
vendor: 'Mock',
Expand All @@ -234,6 +256,7 @@ def create_response_info(
liveness_enabled: liveness_enabled,
classification_info: classification_info,
portrait_match_results: selfie_check_performed? ? portrait_match_results : nil,
passport_check_result:,
}
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

module Proofing
module Mock
class StateIdMockClient
class IdMockClient
SUPPORTED_STATE_ID_TYPES = %w[
drivers_license drivers_permit state_id_card
drivers_license drivers_permit passport state_id_card
].to_set.freeze

INVALID_STATE_ID_NUMBER = '00000000'
Expand All @@ -15,12 +15,14 @@ def proof(applicant)
return mva_timeout_result if mva_timeout?(applicant[:state_id_number])

errors = {}
if state_not_supported?(applicant[:state_id_jurisdiction])
if jurisdiction_not_supported?(applicant)
errors[:state_id_jurisdiction] = ['The jurisdiction could not be verified']
elsif invalid_state_id_number?(applicant[:state_id_number])
errors[:state_id_number] = ['The state ID number could not be verified']
elsif invalid_state_id_type?(applicant[:state_id_type])
errors[:state_id_type] = ['The state ID type could not be verified']
elsif bad_mrz?(applicant)
errors[:mrz] = ['The passport MRZ could not be verified']
end

return unverifiable_result(errors) if errors.any?
Expand Down Expand Up @@ -63,7 +65,10 @@ def mva_timeout?(state_id_number)
state_id_number.downcase == TRIGGER_MVA_TIMEOUT
end

def state_not_supported?(state_id_jurisdiction)
def jurisdiction_not_supported?(applicant)
return false if applicant[:state_id_type] == 'passport'

state_id_jurisdiction = applicant[:state_id_jurisdiction]
!IdentityConfig.store.aamva_supported_jurisdictions.include? state_id_jurisdiction
end

Expand All @@ -75,6 +80,11 @@ def invalid_state_id_type?(state_id_type)
!SUPPORTED_STATE_ID_TYPES.include?(state_id_type) &&
!state_id_type.nil?
end

def bad_mrz?(applicant)
applicant[:state_id_type] == 'passport' &&
applicant[:mrz] == 'bad mrz'
end
end
end
end
2 changes: 1 addition & 1 deletion app/services/proofing/resolution/plugins/aamva_plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def out_of_aamva_jurisdiction_result
def proofer
@proofer ||=
if IdentityConfig.store.proofer_mock_fallback
Proofing::Mock::StateIdMockClient.new
Proofing::Mock::IdMockClient.new
else
Proofing::Aamva::Proofer.new(
auth_request_timeout: IdentityConfig.store.aamva_auth_request_timeout,
Expand Down
62 changes: 62 additions & 0 deletions spec/features/idv/doc_auth/document_capture_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,67 @@
end
end

context 'with a valid passport', allow_browser_log: true do
let(:fake_dos_api_endpoint) { 'http://fake_dos_api_endpoint/' }

before do
stub_request(:post, fake_dos_api_endpoint)
.to_return(status: 200, body: '{"response" : "YES"}', headers: {})

allow(IdentityConfig.store).to receive(:dos_passport_mrz_endpoint)
.and_return(fake_dos_api_endpoint)
visit_idp_from_oidc_sp_with_ial2
sign_in_and_2fa_user(@user)
complete_doc_auth_steps_before_document_capture_step
end

it 'works' do
expect(page).to have_content(t('doc_auth.headings.document_capture'))
expect(page).to have_current_path(idv_document_capture_url)

expect(page).not_to have_content(t('doc_auth.tips.document_capture_selfie_text1'))
attach_images(
Rails.root.join(
'spec', 'fixtures',
'passport_credential.yml'
),
)

submit_images
expect(page).to have_content(t('doc_auth.headings.capture_complete'))
end
end

context 'with an invalid passport', allow_browser_log: true do
let(:fake_dos_api_endpoint) { 'http://fake_dos_api_endpoint/' }

before do
stub_request(:post, fake_dos_api_endpoint)
.to_return(status: 200, body: '{}', headers: {})

allow(IdentityConfig.store).to receive(:dos_passport_mrz_endpoint)
.and_return(fake_dos_api_endpoint)
visit_idp_from_oidc_sp_with_ial2
sign_in_and_2fa_user(@user)
complete_doc_auth_steps_before_document_capture_step
end

it 'fails' do
expect(page).to have_current_path(idv_document_capture_url)
expect(page).not_to have_content(t('doc_auth.tips.document_capture_selfie_text1'))
attach_images(
Rails.root.join(
'spec', 'fixtures',
'passport_bad_mrz_credential.yml'
),
)

submit_images

expect(page).not_to have_content(t('doc_auth.headings.capture_complete'))
end
end

context 'standard desktop flow' do
before do
visit_idp_from_oidc_sp_with_ial2
Expand Down Expand Up @@ -296,6 +357,7 @@
end
end
end

context 'selfie check' do
before do
allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).and_return(true)
Expand Down
4 changes: 2 additions & 2 deletions spec/features/idv/doc_auth/verify_info_step_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@
allow(IdentityConfig.store).to receive(:aamva_supported_jurisdictions).and_return(
mock_state_id_jurisdiction,
)
expect_any_instance_of(Proofing::Mock::StateIdMockClient).to receive(:proof).with(
expect_any_instance_of(Proofing::Mock::IdMockClient).to receive(:proof).with(
hash_including(
**Idp::Constants::MOCK_IDV_APPLICANT,
),
Expand All @@ -271,7 +271,7 @@
IdentityConfig.store.aamva_supported_jurisdictions -
mock_state_id_jurisdiction,
)
expect_any_instance_of(Proofing::Mock::StateIdMockClient).to_not receive(:proof)
expect_any_instance_of(Proofing::Mock::IdMockClient).to_not receive(:proof)

complete_ssn_step
complete_verify_step
Expand Down
2 changes: 1 addition & 1 deletion spec/features/sp_cost_tracking_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
)
expect_sp_cost_type(
5, 2, 'aamva',
transaction_id: Proofing::Mock::StateIdMockClient::TRANSACTION_ID
transaction_id: Proofing::Mock::IdMockClient::TRANSACTION_ID
)
expect_sp_cost_type(6, 2, 'lexis_nexis_address')
end
Expand Down
18 changes: 18 additions & 0 deletions spec/fixtures/passport_bad_mrz_credential.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
passport_check_result:
# returns the DoS API check result
PassportCheckResult: Fail
doc_auth_result: Failed
document:
first_name: Joe
last_name: Dokes
middle_name: Q
dob: 10/06/1938
sex: Male
state_id_type: passport
mrz: 'P<UTOSAMPLE<<COMPANY<<<<<<<<<<<<<<<<<<<<<<<<ACU1234P<5UTO0003067F4003065<<<<<<<<<<<<<<02'
birth_place: birthplace
passport_expiration: 2030-03-15
issuing_country_code: USA
passport_issued: 2015-03-15
nationality_code: USA
document_number: '000000'
14 changes: 14 additions & 0 deletions spec/fixtures/passport_credential.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
document:
first_name: Joe
last_name: Dokes
middle_name: Q
dob: 10/06/1938
sex: Male
state_id_type: passport
mrz: 'P<UTOSAMPLE<<COMPANY<<<<<<<<<<<<<<<<<<<<<<<<ACU1234P<5UTO0003067F4003065<<<<<<<<<<<<<<02'
birth_place: birthplace
passport_expiration: '2030-03-15'
issuing_country_code: USA
passport_issued: '2015-03-15'
nationality_code: USA
document_number: '000000'
Loading