diff --git a/app/controllers/concerns/idv_session.rb b/app/controllers/concerns/idv_session.rb index 136422b59cf..306ae1b5d5e 100644 --- a/app/controllers/concerns/idv_session.rb +++ b/app/controllers/concerns/idv_session.rb @@ -14,7 +14,7 @@ def confirm_idv_session_started def confirm_idv_needed return if effective_user.active_profile.blank? || decorated_session.requested_more_recent_verification? || - liveness_upgrade_required? + strict_ial2_upgrade_required? redirect_to idv_activated_url end @@ -23,8 +23,8 @@ def hybrid_session? session[:doc_capture_user_id].present? end - def liveness_upgrade_required? - sp_session[:ial2_strict] && !effective_user.active_profile&.includes_liveness_check? + def strict_ial2_upgrade_required? + sp_session[:ial2_strict] && !effective_user.active_profile&.strict_ial2_proofed? end def confirm_idv_vendor_session_started diff --git a/app/controllers/idv_controller.rb b/app/controllers/idv_controller.rb index 75afeb1341f..a000e55b56f 100644 --- a/app/controllers/idv_controller.rb +++ b/app/controllers/idv_controller.rb @@ -8,7 +8,7 @@ class IdvController < ApplicationController def index if decorated_session.requested_more_recent_verification? verify_identity - elsif active_profile? && !liveness_upgrade_required? + elsif active_profile? && !strict_ial2_upgrade_required? redirect_to idv_activated_url elsif idv_attempter_throttled? analytics.track_event( @@ -46,8 +46,8 @@ def profile_needs_reactivation? redirect_to reactivate_account_url end - def liveness_upgrade_required? - sp_session[:ial2_strict] && !current_user.active_profile&.includes_liveness_check? + def strict_ial2_upgrade_required? + sp_session[:ial2_strict] && !current_user.active_profile&.strict_ial2_proofed? end def active_profile? diff --git a/app/controllers/openid_connect/authorization_controller.rb b/app/controllers/openid_connect/authorization_controller.rb index 42839b64821..a6ab0006e53 100644 --- a/app/controllers/openid_connect/authorization_controller.rb +++ b/app/controllers/openid_connect/authorization_controller.rb @@ -88,8 +88,11 @@ def identity_needs_verification? ((@authorize_form.ial2_requested? || @authorize_form.ial2_strict_requested?) && (current_user.decorate.identity_not_verified? || decorated_session.requested_more_recent_verification?)) || - (@authorize_form.ial2_strict_requested? && - !current_user.active_profile&.includes_liveness_check?) + identity_needs_strict_ial2_verification? + end + + def identity_needs_strict_ial2_verification? + @authorize_form.ial2_strict_requested? && !current_user.active_profile&.strict_ial2_proofed? end def build_authorize_form_from_params diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index dae70e5e341..4167bcfcf2a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -33,7 +33,7 @@ def ial2_requested? def liveness_checking_enabled? return false if !FeatureManagement.liveness_checking_enabled? return sp_session[:ial2_strict] if sp_session.key?(:ial2_strict) - !!current_user&.decorate&.password_reset_profile&.includes_liveness_check? + !!current_user&.decorate&.password_reset_profile&.strict_ial2_proofed? end def cancel_link_text diff --git a/app/models/profile.rb b/app/models/profile.rb index 325b0e54e76..111e6b262ef 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -90,6 +90,13 @@ def includes_phone_check? proofing_components['address_check'] == 'lexis_nexis_address' end + def strict_ial2_proofed? + return false unless active + return false unless includes_liveness_check? + return true if IdentityConfig.store.usps_upload_allowed_for_strict_ial2 + includes_phone_check? + end + private def personal_key_generator diff --git a/app/services/idv/steps/doc_auth_base_step.rb b/app/services/idv/steps/doc_auth_base_step.rb index 70d645778b8..65aa437f4fe 100644 --- a/app/services/idv/steps/doc_auth_base_step.rb +++ b/app/services/idv/steps/doc_auth_base_step.rb @@ -130,7 +130,7 @@ def sp_session def liveness_checking_enabled? return false if !FeatureManagement.liveness_checking_enabled? return sp_session[:ial2_strict] if sp_session.key?(:ial2_strict) - !!current_user.decorate.password_reset_profile&.includes_liveness_check? + !!current_user.decorate.password_reset_profile&.strict_ial2_proofed? end def create_document_capture_session(key) diff --git a/config/application.yml.default b/config/application.yml.default index 0e3d4dc1ea6..3fd9ccbc7ed 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -242,6 +242,7 @@ usps_ipp_root_url: '' usps_ipp_request_timeout: 10 usps_ipp_sponsor_id: '' usps_ipp_username: '' +usps_upload_allowed_for_strict_ial2: true voice_otp_pause_time: '0.5s' voice_otp_speech_rate: 'slow' voip_check: true diff --git a/lib/identity_config.rb b/lib/identity_config.rb index e6476e6f6f9..6ed64fe6be4 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -325,6 +325,7 @@ def self.build_store(config_map) config.add(:usps_ipp_username, type: :string) config.add(:usps_ipp_request_timeout, type: :integer) config.add(:usps_upload_enabled, type: :boolean) + config.add(:usps_upload_allowed_for_strict_ial2, type: :boolean) config.add(:usps_upload_sftp_directory, type: :string) config.add(:usps_upload_sftp_host, type: :string) config.add(:usps_upload_sftp_password, type: :string) diff --git a/spec/features/idv/liveness/upgrade_to_strong_ial2_spec.rb b/spec/features/idv/liveness/upgrade_to_strong_ial2_spec.rb deleted file mode 100644 index a8c5939bad7..00000000000 --- a/spec/features/idv/liveness/upgrade_to_strong_ial2_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'rails_helper' - -describe 'Strong IAL2' do - include IdvHelper - include OidcAuthHelper - include SamlAuthHelper - include DocAuthHelper - - context 'with SP that sends an IAL2 strict request and a verified profile with no liveness' do - it 'upgrades user to IAL2 strict if liveness checking is enabled' do - allow(IdentityConfig.store).to receive(:liveness_checking_enabled).and_return(true) - - user ||= create( - :profile, :active, :verified, - pii: { first_name: 'John', ssn: '111223333' } - ).user - visit_idp_from_oidc_sp_with_ial2_strict - sign_in_user(user) - fill_in_code_with_last_phone_otp - click_submit_default - click_agree_and_continue_optional - - expect(page.current_path).to eq(idv_doc_auth_welcome_step) - - complete_all_doc_auth_steps - click_continue - fill_in 'Password', with: user.password - click_continue - click_acknowledge_personal_key - click_agree_and_continue - - expect(current_url).to start_with('http://localhost:7654/auth/result') - expect(user.active_profile.includes_liveness_check?).to be_truthy - end - - it 'returns an error if liveness checking is disabled' do - allow(IdentityConfig.store).to receive(:liveness_checking_enabled).and_return(false) - - visit_idp_from_oidc_sp_with_ial2_strict - - expect(current_url).to start_with( - 'http://localhost:7654/auth/result?error=invalid_request'\ - '&error_description=Acr+values+Liveness+checking+is+disabled', - ) - end - end -end diff --git a/spec/features/idv/strict_ial2/feature_flag_spec.rb b/spec/features/idv/strict_ial2/feature_flag_spec.rb new file mode 100644 index 00000000000..15c5f4f1629 --- /dev/null +++ b/spec/features/idv/strict_ial2/feature_flag_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +describe 'Strict IAL2 feature flag' do + include IdvHelper + include OidcAuthHelper + + scenario 'returns an error if liveness checking is disabled' do + allow(IdentityConfig.store).to receive(:liveness_checking_enabled).and_return(false) + + visit_idp_from_oidc_sp_with_ial2_strict + + expect(current_url).to start_with( + 'http://localhost:7654/auth/result?error=invalid_request'\ + '&error_description=Acr+values+Liveness+checking+is+disabled', + ) + end +end diff --git a/spec/features/idv/strict_ial2/upgrade_spec.rb b/spec/features/idv/strict_ial2/upgrade_spec.rb new file mode 100644 index 00000000000..fff19b5612d --- /dev/null +++ b/spec/features/idv/strict_ial2/upgrade_spec.rb @@ -0,0 +1,83 @@ +require 'rails_helper' + +feature 'Strict IAL2 upgrade' do + include IdvHelper + include OidcAuthHelper + include SamlAuthHelper + include DocAuthHelper + + before { allow(IdentityConfig.store).to receive(:liveness_checking_enabled).and_return(true) } + + scenario 'an IAL2 strict request for a user with no liveness triggers an upgrade' do + user = create( + :profile, :active, :verified, + pii: { first_name: 'John', ssn: '111223333' } + ).user + visit_idp_from_oidc_sp_with_ial2_strict + sign_in_user(user) + fill_in_code_with_last_phone_otp + click_submit_default + click_agree_and_continue_optional + + expect(page.current_path).to eq(idv_doc_auth_welcome_step) + + complete_all_doc_auth_steps + click_continue + fill_in 'Password', with: user.password + click_continue + click_acknowledge_personal_key + click_agree_and_continue + + expect(current_url).to start_with('http://localhost:7654/auth/result') + expect(user.active_profile.strict_ial2_proofed?).to be_truthy + end + + context 'strict IAL2 does not allow a phone check' do + before do + allow(IdentityConfig.store).to receive( + :usps_upload_allowed_for_strict_ial2, + ).and_return(false) + end + + scenario 'an IAL2 strict request for a user without a phone check triggers an upgrade' do + user = create( + :profile, :active, :verified, + pii: { first_name: 'John', ssn: '111223333' }, + proofing_components: { liveness_check: :acuant, address_check: :gpo_letter } + ).user + visit_idp_from_oidc_sp_with_ial2_strict + sign_in_user(user) + fill_in_code_with_last_phone_otp + click_submit_default + click_agree_and_continue_optional + + expect(page.current_path).to eq(idv_doc_auth_welcome_step) + + complete_all_doc_auth_steps + click_continue + fill_in 'Password', with: user.password + click_continue + click_acknowledge_personal_key + click_agree_and_continue + + expect(current_url).to start_with('http://localhost:7654/auth/result') + expect(user.active_profile.strict_ial2_proofed?).to be_truthy + end + + scenario 'an IAL2 strict request for a user with a phone check does not trigger an upgrade' do + user = create( + :profile, :active, :verified, + pii: { first_name: 'John', ssn: '111223333' }, + proofing_components: { liveness_check: :acuant, address_check: :lexis_nexis_address } + ).user + visit_idp_from_oidc_sp_with_ial2_strict + sign_in_user(user) + fill_in_code_with_last_phone_otp + click_submit_default + click_agree_and_continue + + expect(current_url).to start_with('http://localhost:7654/auth/result') + expect(user.active_profile.strict_ial2_proofed?).to be_truthy + end + end +end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 5c862d6b545..dff0b640f51 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -106,16 +106,20 @@ end end - context 'current user has profile with liveness' do + context 'current user has profile with strict IAL2' do let(:current_user) do create( :user, profiles: [ create( :profile, + :active, :verified, deactivation_reason: :password_reset, - proofing_components: { liveness_check: DocAuthRouter.doc_auth_vendor }, + proofing_components: { + liveness_check: DocAuthRouter.doc_auth_vendor, + address_check: :lexis_nexis_address, + }, ), ], ) diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb index 291d70d4b0b..edf6d5b5731 100644 --- a/spec/models/profile_spec.rb +++ b/spec/models/profile_spec.rb @@ -85,6 +85,65 @@ end end + describe '#strict_ial2_proofed?' do + it 'returns false if the profile is not active' do + profile = create(:profile, active: false) + + expect(profile.strict_ial2_proofed?).to eq(false) + end + + it 'returns false if the profile does not have liveness' do + proofing_components = { liveness_check: nil, address_check: :lexis_nexis_address } + profile = create(:profile, :active, proofing_components: proofing_components) + + expect(profile.strict_ial2_proofed?).to eq(false) + end + + context 'the letter flow is allowed for strict IAL2' do + before do + allow(IdentityConfig.store).to receive( + :usps_upload_allowed_for_strict_ial2, + ).and_return(true) + end + + it 'returns true for a profile with a phone' do + proofing_components = { liveness_check: :acuant, address_check: :lexis_nexis_address } + profile = create(:profile, :active, proofing_components: proofing_components) + + expect(profile.strict_ial2_proofed?).to eq(true) + end + + it 'return true for a profile with a letter' do + proofing_components = { liveness_check: :acuant, address_check: :gpo_letter } + profile = create(:profile, :active, proofing_components: proofing_components) + + expect(profile.strict_ial2_proofed?).to eq(true) + end + end + + context 'the letter flow is not allowed for strict IAL2' do + before do + allow(IdentityConfig.store).to receive( + :usps_upload_allowed_for_strict_ial2, + ).and_return(false) + end + + it 'returns true for a profile with a phone' do + proofing_components = { liveness_check: :acuant, address_check: :lexis_nexis_address } + profile = create(:profile, :active, proofing_components: proofing_components) + + expect(profile.strict_ial2_proofed?).to eq(true) + end + + it 'return false for a profile with a letter' do + proofing_components = { liveness_check: :acuant, address_check: :gpo_letter } + profile = create(:profile, :active, proofing_components: proofing_components) + + expect(profile.strict_ial2_proofed?).to eq(false) + end + end + end + describe '#encrypt_pii' do subject(:encrypt_pii) { profile.encrypt_pii(pii, user.password) }