diff --git a/Gemfile.lock b/Gemfile.lock index 7f6d1c7bc8c..2cb32e99b65 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -450,7 +450,7 @@ GEM net-ssh (6.1.0) newrelic_rpm (9.7.0) nio4r (2.7.3) - nokogiri (1.16.6) + nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) numbers_and_words (0.11.12) @@ -579,8 +579,7 @@ GEM actionpack (>= 5.0) railties (>= 5.0) retries (0.0.5) - rexml (3.3.6) - strscan + rexml (3.3.7) rotp (6.3.0) rouge (4.2.0) rqrcode (2.1.0) @@ -641,8 +640,8 @@ GEM rubocop-capybara (~> 2.17) rubocop-factory_bot (~> 2.22) ruby-progressbar (1.13.0) - ruby-saml (1.13.0) - nokogiri (>= 1.10.5) + ruby-saml (1.17.0) + nokogiri (>= 1.13.10) rexml ruby-statistics (3.0.2) rubyzip (2.3.2) @@ -684,7 +683,6 @@ GEM stringio (3.1.1) strong_migrations (2.0.0) activerecord (>= 6.1) - strscan (3.1.0) tableparser (1.0.1) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) diff --git a/app/assets/stylesheets/components/_index.scss b/app/assets/stylesheets/components/_index.scss index b1f0d17f752..ca1a7031e0c 100644 --- a/app/assets/stylesheets/components/_index.scss +++ b/app/assets/stylesheets/components/_index.scss @@ -15,7 +15,6 @@ @forward 'header'; @forward 'page-heading'; @forward 'personal-key'; -@forward 'radio-button'; @forward 'spinner-button'; @forward 'spinner-dots'; @forward 'step-indicator'; diff --git a/app/assets/stylesheets/components/_radio-button.scss b/app/assets/stylesheets/components/_radio-button.scss deleted file mode 100644 index a73f50f9b29..00000000000 --- a/app/assets/stylesheets/components/_radio-button.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use 'uswds-core' as *; - -@include at-media('tablet') { - .usa-radio__label-illustrated-large { - .usa-radio__image { - @include u-position('absolute'); - @include u-left(2); - @include u-top(50px); - } - - .usa-radio__label--text { - @include u-margin-left(6); - } - } -} - -.usa-radio__input--tile + .usa-radio__label--illustrated.usa-radio__label-illustrated-large { - .usa-radio__image { - @include u-width(88px); - @include u-height('auto'); - } - - .usa-radio__label--text { - h2, - h3 { - @include u-text('gray-70'); - } - } -} diff --git a/app/controllers/idv/by_mail/enter_code_controller.rb b/app/controllers/idv/by_mail/enter_code_controller.rb index 772bac0757a..519f5105c8e 100644 --- a/app/controllers/idv/by_mail/enter_code_controller.rb +++ b/app/controllers/idv/by_mail/enter_code_controller.rb @@ -101,12 +101,10 @@ def note_if_user_did_not_receive_letter def prepare_for_personal_key unless account_not_ready_to_be_activated? - event, _disavowal_token = create_user_event(:account_verified) + create_user_event(:account_verified) UserAlerts::AlertUserAboutAccountVerified.call( - user: current_user, - date_time: event.created_at, - sp_name: decorated_sp_session.sp_name, + profile: current_user.active_profile, ) flash[:success] = t('account.index.verification.success') end diff --git a/app/controllers/idv/enter_password_controller.rb b/app/controllers/idv/enter_password_controller.rb index ee8efbb2ddc..101d3fb0cdb 100644 --- a/app/controllers/idv/enter_password_controller.rb +++ b/app/controllers/idv/enter_password_controller.rb @@ -49,6 +49,7 @@ def create success: true, fraud_review_pending: idv_session.profile.fraud_review_pending?, fraud_rejection: idv_session.profile.fraud_rejection?, + fraud_pending_reason: idv_session.profile.fraud_pending_reason, gpo_verification_pending: idv_session.profile.gpo_verification_pending?, in_person_verification_pending: idv_session.profile.in_person_verification_pending?, deactivation_reason: idv_session.profile.deactivation_reason, @@ -61,6 +62,7 @@ def create success: true, fraud_review_pending: idv_session.profile.fraud_review_pending?, fraud_rejection: idv_session.profile.fraud_rejection?, + fraud_pending_reason: idv_session.profile.fraud_pending_reason, gpo_verification_pending: idv_session.profile.gpo_verification_pending?, in_person_verification_pending: idv_session.profile.in_person_verification_pending?, deactivation_reason: idv_session.profile.deactivation_reason, @@ -116,6 +118,7 @@ def confirm_current_password in_person_verification_pending: current_user.in_person_pending_profile?, fraud_review_pending: fraud_review_pending?, fraud_rejection: fraud_rejection?, + fraud_pending_reason: nil, **ab_test_analytics_buckets, ) @@ -134,11 +137,9 @@ def init_profile end if idv_session.profile.active? - event, _disavowal_token = create_user_event(:account_verified) + create_user_event(:account_verified) UserAlerts::AlertUserAboutAccountVerified.call( - user: current_user, - date_time: event.created_at, - sp_name: decorated_sp_session.sp_name, + profile: idv_session.profile, ) end end diff --git a/app/controllers/idv/how_to_verify_controller.rb b/app/controllers/idv/how_to_verify_controller.rb index bccd8f12c71..17975d9fdda 100644 --- a/app/controllers/idv/how_to_verify_controller.rb +++ b/app/controllers/idv/how_to_verify_controller.rb @@ -24,7 +24,7 @@ def update if how_to_verify_form_params[:selection] == [] sendable_form_params = {} else - sendable_form_params = how_to_verify_form_params + sendable_form_params = how_to_verify_form_params.to_h.symbolize_keys end analytics.idv_doc_auth_how_to_verify_submitted( diff --git a/app/controllers/idv/session_errors_controller.rb b/app/controllers/idv/session_errors_controller.rb index 9500ca6dd46..129ab660980 100644 --- a/app/controllers/idv/session_errors_controller.rb +++ b/app/controllers/idv/session_errors_controller.rb @@ -95,7 +95,7 @@ def log_event(based_on_limiter: nil) type: params[:action], } - options[:submit_attempts_remaining] = based_on_limiter.remaining_count if based_on_limiter + options[:remaining_submit_attempts] = based_on_limiter.remaining_count if based_on_limiter analytics.idv_session_error_visited(**options) end diff --git a/app/forms/openid_connect_authorize_form.rb b/app/forms/openid_connect_authorize_form.rb index bbe816167a7..d8bc28b5a89 100644 --- a/app/forms/openid_connect_authorize_form.rb +++ b/app/forms/openid_connect_authorize_form.rb @@ -30,7 +30,20 @@ class OpenidConnectAuthorizeForm Saml::Idp::Constants::AAL3_HSPD12_AUTHN_CONTEXT_CLASSREF, Saml::Idp::Constants::AAL2_PHISHING_RESISTANT_AUTHN_CONTEXT_CLASSREF, Saml::Idp::Constants::AAL3_AUTHN_CONTEXT_CLASSREF, - Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF].freeze + Saml::Idp::Constants::AAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::AAL1_AUTHN_CONTEXT_CLASSREF].freeze + IALS_BY_PRIORITY = [Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR, + Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR, + Saml::Idp::Constants::IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::IAL_VERIFIED_ACR, + Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::IAL_AUTH_ONLY_ACR, + Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF].freeze attr_reader(*ATTRS) @@ -119,11 +132,11 @@ def success_redirect_uri end def ial_values - acr_values.filter { |acr| acr.include?('ial') || acr.include?('loa') } + IALS_BY_PRIORITY & acr_values end def aal_values - acr_values.filter { |acr| acr.include?('aal') } + AALS_BY_PRIORITY & acr_values end def requested_aal_value @@ -299,7 +312,8 @@ def scopes def validate_privileges if (identity_proofing_requested? && !identity_proofing_service_provider?) || (ialmax_requested? && !ialmax_allowed_for_sp?) || - (biometric_ial_requested? && !service_provider.biometric_ial_allowed?) + (biometric_ial_requested? && !service_provider.biometric_ial_allowed?) || + (semantic_authn_contexts_requested? && !service_provider.semantic_authn_contexts_allowed?) errors.add( :acr_values, t('openid_connect.authorization.errors.no_auth'), type: :no_auth @@ -348,4 +362,8 @@ def highest_level_aal(aal_values) def verified_within_allowed? IdentityConfig.store.allowed_verified_within_providers.include?(client_id) end + + def semantic_authn_contexts_requested? + Saml::Idp::Constants::SEMANTIC_ACRS.intersect?(acr_values) + end end diff --git a/app/helpers/opt_in_helper.rb b/app/helpers/opt_in_helper.rb index 5db0323aa5c..f33694dd0d5 100644 --- a/app/helpers/opt_in_helper.rb +++ b/app/helpers/opt_in_helper.rb @@ -4,8 +4,8 @@ module OptInHelper def opt_in_analytics_properties if IdentityConfig.store.in_person_proofing_opt_in_enabled { opted_in_to_in_person_proofing: idv_session.opted_in_to_in_person_proofing } - else - {} - end + else + { opted_in_to_in_person_proofing: nil } + end end end diff --git a/app/jobs/reports/combined_invoice_supplement_report.rb b/app/jobs/reports/combined_invoice_supplement_report.rb deleted file mode 100644 index fad65ab07c4..00000000000 --- a/app/jobs/reports/combined_invoice_supplement_report.rb +++ /dev/null @@ -1,133 +0,0 @@ -# frozen_string_literal: true - -require 'csv' - -module Reports - class CombinedInvoiceSupplementReport < BaseReport - REPORT_NAME = 'combined-invoice-supplement-report' - - def perform(_date) - csv = build_csv(IaaReportingHelper.iaas) - - save_report(REPORT_NAME, csv, extension: 'csv') - end - - # @param [Array] iaas - # @return [String] CSV report - def build_csv(iaas) - by_iaa_results = iaas.flat_map do |iaa| - Db::MonthlySpAuthCount::UniqueMonthlyAuthCountsByIaa.call( - key: iaa.key, - issuers: iaa.issuers, - start_date: iaa.start_date, - end_date: iaa.end_date, - ) - end - - by_issuer_results = iaas.flat_map do |iaa| - iaa.issuers.flat_map do |issuer| - Db::MonthlySpAuthCount::TotalMonthlyAuthCountsWithinIaaWindow.call( - issuer: issuer, - iaa_start_date: iaa.start_date, - iaa_end_date: iaa.end_date, - iaa: iaa.key, - ) - end - end - - combine_by_iaa_month( - by_iaa_results: by_iaa_results, - by_issuer_results: by_issuer_results, - ) - end - - def combine_by_iaa_month(by_iaa_results:, by_issuer_results:) - by_iaa_and_year_month = by_iaa_results.group_by do |result| - [result[:key], result[:year_month]] - end - - by_issuer_iaa_issuer_year_months = by_issuer_results. - group_by { |r| r[:iaa] }. - transform_values do |iaa| - iaa.group_by { |r| r[:issuer] }. - transform_values { |issuer| issuer.group_by { |r| r[:year_month] } } - end - - # rubocop:disable Metrics/BlockLength - CSV.generate do |csv| - csv << [ - 'iaa_order_number', - 'iaa_start_date', - 'iaa_end_date', - - 'issuer', - 'friendly_name', - - 'year_month', - 'year_month_readable', - - 'iaa_ial1_unique_users', - 'iaa_ial2_unique_users', - 'iaa_ial1_plus_2_unique_users', - 'iaa_ial2_new_unique_users', - - 'issuer_ial1_total_auth_count', - 'issuer_ial2_total_auth_count', - 'issuer_ial1_plus_2_total_auth_count', - - 'issuer_ial1_unique_users', - 'issuer_ial2_unique_users', - 'issuer_ial1_plus_2_unique_users', - 'issuer_ial2_new_unique_users', - ] - - by_issuer_iaa_issuer_year_months.each do |iaa_key, issuer_year_months| - issuer_year_months.each do |issuer, year_months_data| - friendly_name = ServiceProvider.find_by(issuer: issuer).friendly_name - year_months = year_months_data.keys.sort - - year_months.each do |year_month| - iaa_results = by_iaa_and_year_month[ [iaa_key, year_month] ] - issuer_results = year_months_data[year_month] - - year_month_start = Date.strptime(year_month, '%Y%m') - iaa_start_date = Date.parse(iaa_results.first[:iaa_start_date]) - iaa_end_date = Date.parse(iaa_results.first[:iaa_end_date]) - - csv << [ - iaa_key, - iaa_start_date, - iaa_end_date, - - issuer, - friendly_name, - - year_month, - year_month_start.strftime('%B %Y'), - - (iaa_ial1_unique_users = extract(iaa_results, :unique_users, ial: 1)), - (iaa_ial2_unique_users = extract(iaa_results, :unique_users, ial: 2)), - iaa_ial1_unique_users + iaa_ial2_unique_users, - extract(iaa_results, :new_unique_users, ial: 2), - - (ial1_total_auth_count = extract(issuer_results, :total_auth_count, ial: 1)), - (ial2_total_auth_count = extract(issuer_results, :total_auth_count, ial: 2)), - ial1_total_auth_count + ial2_total_auth_count, - - (issuer_ial1_unique_users = extract(issuer_results, :unique_users, ial: 1)), - (issuer_ial2_unique_users = extract(issuer_results, :unique_users, ial: 2)), - issuer_ial1_unique_users + issuer_ial2_unique_users, - extract(issuer_results, :new_unique_users, ial: 2), - ] - end - end - end - end - # rubocop:enable Metrics/BlockLength - end - - def extract(arr, key, ial:) - arr.find { |elem| elem[:ial] == ial && elem[key] }&.dig(key) || 0 - end - end -end diff --git a/app/models/federated_protocols/oidc.rb b/app/models/federated_protocols/oidc.rb index 9364cc38f5f..2486241580b 100644 --- a/app/models/federated_protocols/oidc.rb +++ b/app/models/federated_protocols/oidc.rb @@ -11,11 +11,11 @@ def issuer end def ial - request.ial_values.sort.max + request.ial_values.first end def aal - request.aal_values.sort.max + request.aal_values.first end def acr_values diff --git a/app/models/federated_protocols/saml.rb b/app/models/federated_protocols/saml.rb index a87face7dea..cd63e21e583 100644 --- a/app/models/federated_protocols/saml.rb +++ b/app/models/federated_protocols/saml.rb @@ -2,8 +2,6 @@ module FederatedProtocols class Saml - IAL_PREFIX = %r{^http://idmanagement.gov/ns/assurance/ial} - LOA_PREFIX = %r{^http://idmanagement.gov/ns/assurance/loa} AAL_PREFIX = %r{^http://idmanagement.gov/ns/assurance/aal|urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo} def initialize(request) @@ -23,9 +21,7 @@ def ial end def requested_ial_authn_context - request.requested_authn_contexts.find do |classref| - IAL_PREFIX.match?(classref) || LOA_PREFIX.match?(classref) - end + (OpenidConnectAuthorizeForm::IALS_BY_PRIORITY & request.requested_authn_contexts).first end def aal diff --git a/app/models/service_provider.rb b/app/models/service_provider.rb index 9cf47341171..f448eeb7cd2 100644 --- a/app/models/service_provider.rb +++ b/app/models/service_provider.rb @@ -70,11 +70,24 @@ def skip_encryption_allowed @allowed_list.include? issuer end + def identity_proofing_allowed? + ial.present? && ial >= 2 + end + + def ialmax_allowed? + IdentityConfig.store.allowed_ialmax_providers.include?(issuer) + end + def biometric_ial_allowed? IdentityConfig.store.biometric_ial_enabled && IdentityConfig.store.allowed_biometric_ial_providers.include?(issuer) end + def semantic_authn_contexts_allowed? + IdentityConfig.store.feature_valid_authn_contexts_semantic_enabled && + IdentityConfig.store.allowed_valid_authn_contexts_semantic_providers.include?(issuer) + end + private # @return [String,nil] diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index 42db7c5db0c..f01ca2b86a9 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -406,12 +406,16 @@ def create_new_device_alert_job_emails_sent(count:, **extra) end # @param [String] message the warning + # @param [Array] unknown_alerts Names of alerts not recognized by our code + # @param [Hash] response_info Response payload # Logged when there is a non-user-facing error in the doc auth process, such as an unrecognized # field from a vendor - def doc_auth_warning(message: nil, **extra) + def doc_auth_warning(message: nil, unknown_alerts: nil, response_info: nil, **extra) track_event( 'Doc Auth Warning', - message: message, + message:, + unknown_alerts:, + response_info:, **extra, ) end @@ -1092,16 +1096,20 @@ def idv_consent_checkbox_toggled(checked:, **extra) # view the "hybrid handoff" step next unless "skip_hybrid_handoff" param is true # @param [Boolean] success Whether form validation was successful # @param [Hash] errors Errors resulting from form validation + # @param [Hash] error_details Details for errors that occurred in unsuccessful submission # @param [String] step Current IdV step # @param [String] analytics_id Current IdV flow identifier # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # SDK upgrades # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_agreement_submitted( success:, errors:, step:, analytics_id:, + opted_in_to_in_person_proofing: nil, + error_details: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, **extra @@ -1110,10 +1118,12 @@ def idv_doc_auth_agreement_submitted( 'IdV: doc auth agreement submitted', success:, errors:, + error_details:, step:, analytics_id:, acuant_sdk_upgrade_ab_test_bucket:, skip_hybrid_handoff:, + opted_in_to_in_person_proofing:, **extra, ) end @@ -1124,9 +1134,11 @@ def idv_doc_auth_agreement_submitted( # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # SDK upgrades # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_agreement_visited( step:, analytics_id:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, **extra @@ -1137,12 +1149,30 @@ def idv_doc_auth_agreement_visited( analytics_id:, acuant_sdk_upgrade_ab_test_bucket:, skip_hybrid_handoff:, + opted_in_to_in_person_proofing:, **extra, ) end - def idv_doc_auth_capture_complete_visited(**extra) - track_event('IdV: doc auth capture_complete visited', **extra) + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param ["hybrid","standard"] flow_path Document capture user flow + # @param [Boolean] liveness_checking_required Whether biometric selfie check is required + def idv_doc_auth_capture_complete_visited( + step:, + analytics_id:, + flow_path:, + liveness_checking_required:, + **extra + ) + track_event( + 'IdV: doc auth capture_complete visited', + step:, + analytics_id:, + flow_path:, + liveness_checking_required:, + **extra, + ) end # User submits IdV document capture step @@ -1158,6 +1188,8 @@ def idv_doc_auth_capture_complete_visited(**extra) # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # SDK upgrades + # @param [Boolean] stored_result_present Whether a stored result was present + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_document_capture_submitted( success:, errors:, @@ -1166,9 +1198,11 @@ def idv_doc_auth_document_capture_submitted( liveness_checking_required:, selfie_check_required:, flow_path:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, redo_document_capture: nil, skip_hybrid_handoff: nil, + stored_result_present: nil, **extra ) track_event( @@ -1183,6 +1217,8 @@ def idv_doc_auth_document_capture_submitted( selfie_check_required:, acuant_sdk_upgrade_ab_test_bucket:, flow_path:, + opted_in_to_in_person_proofing:, + stored_result_present:, **extra, ) end @@ -1198,12 +1234,14 @@ def idv_doc_auth_document_capture_submitted( # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # SDK upgrades # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_document_capture_visited( step:, analytics_id:, liveness_checking_required:, selfie_check_required:, flow_path:, + opted_in_to_in_person_proofing: nil, redo_document_capture: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, @@ -1218,6 +1256,7 @@ def idv_doc_auth_document_capture_visited( skip_hybrid_handoff:, liveness_checking_required:, selfie_check_required:, + opted_in_to_in_person_proofing:, acuant_sdk_upgrade_ab_test_bucket:, **extra, ) @@ -1245,12 +1284,58 @@ def idv_doc_auth_failed_image_resubmitted(side:, **extra) ) end - def idv_doc_auth_how_to_verify_submitted(**extra) - track_event(:idv_doc_auth_how_to_verify_submitted, **extra) + # @param [Boolean] success Whether form validation was successful + # @param [Hash] errors Errors resulting from form validation + # @param [Hash] error_details Details for errors that occurred in unsuccessful submission + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [String] selection Selection form parameter + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + def idv_doc_auth_how_to_verify_submitted( + success:, + errors:, + step:, + analytics_id:, + skip_hybrid_handoff:, + opted_in_to_in_person_proofing: nil, + selection: nil, + error_details: nil, + **extra + ) + track_event( + :idv_doc_auth_how_to_verify_submitted, + success:, + errors:, + error_details:, + step:, + analytics_id:, + skip_hybrid_handoff:, + selection:, + opted_in_to_in_person_proofing:, + **extra, + ) end - def idv_doc_auth_how_to_verify_visited(**extra) - track_event(:idv_doc_auth_how_to_verify_visited, **extra) + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + def idv_doc_auth_how_to_verify_visited( + step:, + analytics_id:, + skip_hybrid_handoff:, + opted_in_to_in_person_proofing: nil, + **extra + ) + track_event( + :idv_doc_auth_how_to_verify_visited, + step:, + analytics_id:, + skip_hybrid_handoff:, + opted_in_to_in_person_proofing:, + **extra, + ) end # The "hybrid handoff" step: Desktop user has submitted their choice to @@ -1269,6 +1354,8 @@ def idv_doc_auth_how_to_verify_visited(**extra) # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # SDK upgrades # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Hash] telephony_response Response from Telephony gem + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_hybrid_handoff_submitted( success:, errors:, @@ -1278,8 +1365,10 @@ def idv_doc_auth_hybrid_handoff_submitted( selfie_check_required:, destination:, flow_path:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, + telephony_response: nil, **extra ) track_event( @@ -1292,8 +1381,10 @@ def idv_doc_auth_hybrid_handoff_submitted( skip_hybrid_handoff:, selfie_check_required:, acuant_sdk_upgrade_ab_test_bucket:, + opted_in_to_in_person_proofing:, destination:, flow_path:, + telephony_response:, **extra, ) end @@ -1308,11 +1399,13 @@ def idv_doc_auth_hybrid_handoff_submitted( # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # SDK upgrades # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_hybrid_handoff_visited( step:, analytics_id:, redo_document_capture:, selfie_check_required:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, **extra @@ -1323,23 +1416,87 @@ def idv_doc_auth_hybrid_handoff_visited( analytics_id:, redo_document_capture:, skip_hybrid_handoff:, + opted_in_to_in_person_proofing:, selfie_check_required:, acuant_sdk_upgrade_ab_test_bucket:, **extra, ) end + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param ["hybrid","standard"] flow_path Document capture user flow + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # @identity.idp.previous_event_name IdV: doc auth send_link submitted - def idv_doc_auth_link_sent_submitted(**extra) - track_event('IdV: doc auth link_sent submitted', **extra) + def idv_doc_auth_link_sent_submitted( + step:, + analytics_id:, + flow_path:, + opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, + **extra + ) + track_event( + 'IdV: doc auth link_sent submitted', + step:, + analytics_id:, + flow_path:, + opted_in_to_in_person_proofing:, + skip_hybrid_handoff:, + **extra, + ) end - def idv_doc_auth_link_sent_visited(**extra) - track_event('IdV: doc auth link_sent visited', **extra) + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param ["hybrid","standard"] flow_path Document capture user flow + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + def idv_doc_auth_link_sent_visited( + step:, + analytics_id:, + flow_path:, + opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, + **extra + ) + track_event( + 'IdV: doc auth link_sent visited', + step:, + analytics_id:, + flow_path:, + opted_in_to_in_person_proofing:, + skip_hybrid_handoff:, + **extra, + ) end - def idv_doc_auth_redo_ssn_submitted(**extra) - track_event('IdV: doc auth redo_ssn submitted', **extra) + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param ["hybrid","standard"] flow_path Document capture user flow + # @param [Boolean] same_address_as_id + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + def idv_doc_auth_redo_ssn_submitted( + step:, + analytics_id:, + flow_path:, + opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, + same_address_as_id: nil, + **extra + ) + track_event( + 'IdV: doc auth redo_ssn submitted', + step:, + analytics_id:, + flow_path:, + opted_in_to_in_person_proofing:, + skip_hybrid_handoff:, + same_address_as_id:, + **extra, + ) end # @param [String] created_at The created timestamp received from Socure @@ -1370,30 +1527,39 @@ def idv_doc_auth_socure_webhook_received( # @identity.idp.previous_event_name IdV: in person proofing ssn submitted # @param [Boolean] success Whether form validation was successful # @param [Hash] errors Errors resulting from form validation + # @param [Hash] error_details Details for errors that occurred in unsuccessful submission # @param [String] step Current IdV step # @param [String] analytics_id Current IdV flow identifier # @param ["hybrid","standard"] flow_path Document capture user flow # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] same_address_as_id + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_ssn_submitted( success:, errors:, step:, analytics_id:, flow_path:, + opted_in_to_in_person_proofing: nil, + error_details: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, + same_address_as_id: nil, **extra ) track_event( 'IdV: doc auth ssn submitted', success:, errors:, + error_details:, step:, analytics_id:, skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, flow_path:, + opted_in_to_in_person_proofing:, + same_address_as_id:, **extra, ) end @@ -1405,12 +1571,16 @@ def idv_doc_auth_ssn_submitted( # @param ["hybrid","standard"] flow_path Document capture user flow # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] same_address_as_id + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_ssn_visited( step:, analytics_id:, flow_path:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, + same_address_as_id: nil, **extra ) track_event( @@ -1420,6 +1590,8 @@ def idv_doc_auth_ssn_visited( skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, flow_path:, + opted_in_to_in_person_proofing:, + same_address_as_id:, **extra, ) end @@ -1492,7 +1664,8 @@ def idv_doc_auth_submitted_image_upload_form( # @param [Boolean] attention_with_barcode Whether result was attention with barcode # @param [Boolean] doc_type_supported # @param [Boolean] doc_auth_success - # @param [String] liveness_checking_required Whether or not the selfie is required + # @param [Boolean] liveness_checking_required Whether or not the selfie is required + # @param [Boolean] liveness_enabled Whether or not the selfie result is included in response # @param [String] selfie_status # @param [String] vendor # @param [String] conversation_id @@ -1514,6 +1687,8 @@ def idv_doc_auth_submitted_image_upload_form( # @param [String] workflow LexisNexis TrueID workflow # @param [String] birth_year Birth year from document # @param [Integer] issue_year Year document was issued + # @param [Hash] failed_image_fingerprints Hash of document field with an array of failed image + # fingerprints for that field. # @param [Integer] selfie_attempts number of selfie attempts the user currently has processed # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # SDK upgrades @@ -1545,6 +1720,7 @@ def idv_doc_auth_submitted_image_upload_vendor( flow_path:, liveness_checking_required:, issue_year:, + failed_image_fingerprints: nil, billed: nil, doc_auth_result: nil, vendor_request_time_in_ms: nil, @@ -1576,6 +1752,7 @@ def idv_doc_auth_submitted_image_upload_vendor( birth_year: nil, selfie_attempts: nil, acuant_sdk_upgrade_ab_test_bucket: nil, + liveness_enabled: nil, **extra ) track_event( @@ -1621,8 +1798,10 @@ def idv_doc_auth_submitted_image_upload_vendor( workflow:, birth_year:, issue_year:, + failed_image_fingerprints:, selfie_attempts:, acuant_sdk_upgrade_ab_test_bucket:, + liveness_enabled:, **extra, ) end @@ -1698,6 +1877,7 @@ def idv_doc_auth_verify_polling_wait_visited(**extra) # @param flow_path [String] "hybrid" for hybrid handoff, "standard" otherwise # @param lexisnexis_instant_verify_workflow_ab_test_bucket [String] A/B test bucket for Lexis Nexis InstantVerify workflow testing # @param opted_in_to_in_person_proofing [Boolean] Whether this user explicitly opted into in-person proofing + # @param [Boolean] same_address_as_id # @param proofing_results [Hash] # @option proofing_results [String,nil] exception If an exception occurred during any phase of proofing its message is provided here # @option proofing_results [Boolean] timed_out true if any vendor API calls timed out during proofing @@ -1768,6 +1948,7 @@ def idv_doc_auth_verify_proofing_results( ssn_is_unique: nil, step: nil, success: nil, + same_address_as_id: nil, **extra ) track_event( @@ -1786,6 +1967,7 @@ def idv_doc_auth_verify_proofing_results( ssn_is_unique:, step:, success:, + same_address_as_id:, **extra, ) end @@ -1798,12 +1980,16 @@ def idv_doc_auth_verify_proofing_results( # @param ["hybrid","standard"] flow_path Document capture user flow # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] same_address_as_id + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_verify_submitted( step:, analytics_id:, flow_path:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, + same_address_as_id: nil, **extra ) track_event( @@ -1813,6 +1999,8 @@ def idv_doc_auth_verify_submitted( skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, flow_path:, + opted_in_to_in_person_proofing:, + same_address_as_id:, **extra, ) end @@ -1824,12 +2012,16 @@ def idv_doc_auth_verify_submitted( # @param ["hybrid","standard"] flow_path Document capture user flow # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] same_address_as_id + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing def idv_doc_auth_verify_visited( step:, analytics_id:, flow_path:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, + same_address_as_id: nil, **extra ) track_event( @@ -1839,6 +2031,8 @@ def idv_doc_auth_verify_visited( skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, flow_path:, + opted_in_to_in_person_proofing:, + same_address_as_id:, **extra, ) end @@ -1859,11 +2053,19 @@ def idv_doc_auth_warning_visited(step_name:, remaining_submit_attempts:, **extra # @param [String] step Current IdV step # @param [String] analytics_id Current IdV flow identifier # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active - def idv_doc_auth_welcome_submitted(step:, analytics_id:, skip_hybrid_handoff: nil, **extra) + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + def idv_doc_auth_welcome_submitted( + step:, + analytics_id:, + opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, + **extra + ) track_event( 'IdV: doc auth welcome submitted', step:, analytics_id:, + opted_in_to_in_person_proofing:, skip_hybrid_handoff:, **extra, ) @@ -1873,12 +2075,20 @@ def idv_doc_auth_welcome_submitted(step:, analytics_id:, skip_hybrid_handoff: ni # @param [String] step Current IdV step # @param [String] analytics_id Current IdV flow identifier # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active - def idv_doc_auth_welcome_visited(step:, analytics_id:, skip_hybrid_handoff: nil, **extra) + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + def idv_doc_auth_welcome_visited( + step:, + analytics_id:, + opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, + **extra + ) track_event( 'IdV: doc auth welcome visited', step:, analytics_id:, skip_hybrid_handoff:, + opted_in_to_in_person_proofing:, **extra, ) end @@ -1887,6 +2097,7 @@ def idv_doc_auth_welcome_visited(step:, analytics_id:, skip_hybrid_handoff: nil, # @param [Boolean] success # @param [Boolean] fraud_review_pending # @param [Boolean] fraud_rejection + # @param [String,nil] fraud_pending_reason The reason this profile is eligible for fraud review # @param [Boolean] gpo_verification_pending # @param [Boolean] in_person_verification_pending # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture @@ -1903,13 +2114,16 @@ def idv_doc_auth_welcome_visited(step:, analytics_id:, skip_hybrid_handoff: nil, # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # @param [Integer,nil] proofing_workflow_time_in_seconds The time since starting proofing + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # @identity.idp.previous_event_name IdV: review info visited def idv_enter_password_submitted( success:, fraud_review_pending:, fraud_rejection:, + fraud_pending_reason:, gpo_verification_pending:, in_person_verification_pending:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, deactivation_reason: nil, @@ -1924,10 +2138,12 @@ def idv_enter_password_submitted( success:, deactivation_reason:, fraud_review_pending:, + fraud_pending_reason:, gpo_verification_pending:, in_person_verification_pending:, skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, + opted_in_to_in_person_proofing:, fraud_rejection:, proofing_components:, active_profile_idv_level:, @@ -1951,9 +2167,11 @@ def idv_enter_password_submitted( # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # used to verify the user's identity + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # User visited IDV password confirm page # @identity.idp.previous_event_name IdV: review info visited def idv_enter_password_visited( + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, proofing_components: nil, @@ -1964,6 +2182,7 @@ def idv_enter_password_visited( ) track_event( :idv_enter_password_visited, + opted_in_to_in_person_proofing:, skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, address_verification_method:, @@ -1978,6 +2197,7 @@ def idv_enter_password_visited( # @param [String, nil] deactivation_reason Reason user's profile was deactivated, if any. # @param [Boolean] fraud_review_pending Profile is under review for fraud # @param [Boolean] fraud_rejection Profile is rejected due to fraud + # @param [String,nil] fraud_pending_reason The reason this profile is eligible for fraud review # @param [Boolean] gpo_verification_pending Profile is awaiting gpo verification # @param [Boolean] in_person_verification_pending Profile is awaiting in person verification # @param [String] acuant_sdk_upgrade_ab_test_bucket A/B test bucket for Acuant document capture @@ -1994,6 +2214,7 @@ def idv_enter_password_visited( # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. # @param [Array,nil] profile_history Array of user's profiles (oldest to newest). # @param [Integer,nil] proofing_workflow_time_in_seconds The time since starting proofing + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # @see Reporting::IdentityVerificationReport#query This event is used by the identity verification # report. Changes here should be reflected there. # Tracks the last step of IDV, indicates the user successfully proofed @@ -2001,8 +2222,10 @@ def idv_final( success:, fraud_review_pending:, fraud_rejection:, + fraud_pending_reason:, gpo_verification_pending:, in_person_verification_pending:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, deactivation_reason: nil, @@ -2018,8 +2241,10 @@ def idv_final( success:, fraud_review_pending:, fraud_rejection:, + fraud_pending_reason:, gpo_verification_pending:, in_person_verification_pending:, + opted_in_to_in_person_proofing:, skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, deactivation_reason:, @@ -2221,6 +2446,8 @@ def idv_front_image_clicked( # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active # GPO letter was enqueued and the time at which it was enqueued def idv_gpo_address_letter_enqueued( enqueued_at:, @@ -2228,6 +2455,8 @@ def idv_gpo_address_letter_enqueued( first_letter_requested_at:, hours_since_first_letter:, phone_step_attempts:, + opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, proofing_components: nil, active_profile_idv_level: nil, pending_profile_idv_level: nil, @@ -2235,14 +2464,16 @@ def idv_gpo_address_letter_enqueued( ) track_event( 'IdV: USPS address letter enqueued', - enqueued_at: enqueued_at, - resend: resend, - first_letter_requested_at: first_letter_requested_at, - hours_since_first_letter: hours_since_first_letter, - phone_step_attempts: phone_step_attempts, - proofing_components: proofing_components, - active_profile_idv_level: active_profile_idv_level, - pending_profile_idv_level: pending_profile_idv_level, + enqueued_at:, + resend:, + first_letter_requested_at:, + hours_since_first_letter:, + phone_step_attempts:, + opted_in_to_in_person_proofing:, + skip_hybrid_handoff:, + proofing_components:, + active_profile_idv_level:, + pending_profile_idv_level:, **extra, ) end @@ -2262,12 +2493,16 @@ def idv_gpo_address_letter_enqueued( # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active # GPO letter was requested def idv_gpo_address_letter_requested( resend:, first_letter_requested_at:, hours_since_first_letter:, phone_step_attempts:, + opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, proofing_components: nil, active_profile_idv_level: nil, pending_profile_idv_level: nil, @@ -2275,13 +2510,15 @@ def idv_gpo_address_letter_requested( ) track_event( 'IdV: USPS address letter requested', - resend: resend, + resend:, first_letter_requested_at:, hours_since_first_letter:, phone_step_attempts:, - proofing_components: proofing_components, - active_profile_idv_level: active_profile_idv_level, - pending_profile_idv_level: pending_profile_idv_level, + opted_in_to_in_person_proofing:, + skip_hybrid_handoff:, + proofing_components:, + active_profile_idv_level:, + pending_profile_idv_level:, **extra, ) end @@ -2398,7 +2635,7 @@ def idv_in_person_email_reminder_job_exception( def idv_in_person_location_submitted( selected_location:, flow_path:, - opted_in_to_in_person_proofing:, + opted_in_to_in_person_proofing: nil, **extra ) track_event( @@ -2423,12 +2660,14 @@ def idv_in_person_location_visited(flow_path:, opted_in_to_in_person_proofing:, end # Tracks if request to get USPS in-person proofing locations fails + # @param [Integer] api_status_code HTTP status code for API response # @param [String] exception_class # @param [String] exception_message # @param [Boolean] response_body_present # @param [Hash] response_body # @param [Integer] response_status_code def idv_in_person_locations_request_failure( + api_status_code:, exception_class:, exception_message:, response_body_present:, @@ -2438,11 +2677,12 @@ def idv_in_person_locations_request_failure( ) track_event( 'Request USPS IPP locations: request failed', - exception_class: exception_class, - exception_message: exception_message, - response_body_present: response_body_present, - response_body: response_body, - response_status_code: response_status_code, + api_status_code:, + exception_class:, + exception_message:, + response_body_present:, + response_body:, + response_status_code:, **extra, ) end @@ -2502,21 +2742,27 @@ def idv_in_person_prepare_visited(flow_path:, opted_in_to_in_person_proofing:, * # @param ["hybrid","standard"] flow_path Document capture user flow # @param [String] step # @param [String] analytics_id + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] same_address_as_id # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # address page visited def idv_in_person_proofing_address_visited( - flow_path: nil, - step: nil, - analytics_id: nil, + flow_path:, + step:, + analytics_id:, opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, + same_address_as_id: nil, **extra ) track_event( 'IdV: in person proofing address visited', - flow_path: flow_path, - step: step, - analytics_id: analytics_id, - opted_in_to_in_person_proofing: opted_in_to_in_person_proofing, + flow_path:, + step:, + analytics_id:, + opted_in_to_in_person_proofing:, + skip_hybrid_handoff:, + same_address_as_id:, **extra, ) end @@ -2628,6 +2874,7 @@ def idv_in_person_proofing_nontransliterable_characters_submitted( # @param [Hash] errors Errors resulting from form validation # @param [Hash] error_details Details for errors that occurred in unsuccessful submission # @param [Boolean] same_address_as_id + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # User submitted state id on redo state id page def idv_in_person_proofing_redo_state_id_submitted( success:, @@ -2637,6 +2884,7 @@ def idv_in_person_proofing_redo_state_id_submitted( step: nil, analytics_id: nil, same_address_as_id: nil, + opted_in_to_in_person_proofing: nil, **extra ) track_event( @@ -2648,12 +2896,48 @@ def idv_in_person_proofing_redo_state_id_submitted( errors:, error_details:, same_address_as_id:, + opted_in_to_in_person_proofing:, **extra, ) end - def idv_in_person_proofing_residential_address_submitted(**extra) - track_event('IdV: in person proofing residential address submitted', **extra) + # @param [Boolean] success Whether form validation was successful + # @param [Hash] errors Errors resulting from form validation + # @param [Hash] error_details Details for errors that occurred in unsuccessful submission + # @param ["hybrid","standard"] flow_path Document capture user flow + # @param [String] step Current IdV step + # @param [String] analytics_id Current IdV flow identifier + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean, nil] same_address_as_id + # @param [String] current_address_zip_code ZIP code of given address + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + def idv_in_person_proofing_residential_address_submitted( + success:, + errors:, + flow_path:, + step:, + analytics_id:, + current_address_zip_code:, + opted_in_to_in_person_proofing: nil, + error_details: nil, + skip_hybrid_handoff: nil, + same_address_as_id: nil, + **extra + ) + track_event( + 'IdV: in person proofing residential address submitted', + success:, + errors:, + flow_path:, + step:, + analytics_id:, + current_address_zip_code:, + opted_in_to_in_person_proofing:, + error_details:, + skip_hybrid_handoff:, + same_address_as_id:, + **extra, + ) end # @param ["hybrid","standard"] flow_path Document capture user flow @@ -2664,14 +2948,20 @@ def idv_in_person_proofing_residential_address_submitted(**extra) # @param [Hash] error_details Details for errors that occurred in unsuccessful submission # @param [Boolean, nil] same_address_as_id # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + # @param [String] birth_year Birth year from document + # @param [String] document_zip_code ZIP code from document + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active # User submitted state id def idv_in_person_proofing_state_id_submitted( success:, errors:, + flow_path:, + step:, + analytics_id:, + birth_year:, + document_zip_code:, + skip_hybrid_handoff: nil, error_details: nil, - flow_path: nil, - step: nil, - analytics_id: nil, same_address_as_id: nil, opted_in_to_in_person_proofing: nil, **extra @@ -2684,6 +2974,9 @@ def idv_in_person_proofing_state_id_submitted( success:, errors:, error_details:, + birth_year:, + document_zip_code:, + skip_hybrid_handoff:, same_address_as_id:, opted_in_to_in_person_proofing:, **extra, @@ -2694,20 +2987,26 @@ def idv_in_person_proofing_state_id_submitted( # @param [String] step # @param [String] analytics_id # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + # @param [Boolean] same_address_as_id + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active # State id page visited def idv_in_person_proofing_state_id_visited( flow_path: nil, step: nil, analytics_id: nil, opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, + same_address_as_id: nil, **extra ) track_event( 'IdV: in person proofing state_id visited', - flow_path: flow_path, - step: step, - analytics_id: analytics_id, - opted_in_to_in_person_proofing: opted_in_to_in_person_proofing, + flow_path:, + step:, + analytics_id:, + opted_in_to_in_person_proofing:, + skip_hybrid_handoff:, + same_address_as_id:, **extra, ) end @@ -2730,16 +3029,21 @@ def idv_in_person_ready_to_verify_sp_link_clicked(**extra) # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # The user visited the "ready to verify" page for the in person proofing flow - def idv_in_person_ready_to_verify_visit(proofing_components: nil, - active_profile_idv_level: nil, - pending_profile_idv_level: nil, - **extra) + def idv_in_person_ready_to_verify_visit( + opted_in_to_in_person_proofing: nil, + proofing_components: nil, + active_profile_idv_level: nil, + pending_profile_idv_level: nil, + **extra + ) track_event( 'IdV: in person ready to verify visited', - proofing_components: proofing_components, - active_profile_idv_level: active_profile_idv_level, - pending_profile_idv_level: pending_profile_idv_level, + opted_in_to_in_person_proofing:, + proofing_components:, + active_profile_idv_level:, + pending_profile_idv_level:, **extra, ) end @@ -2756,7 +3060,7 @@ def idv_in_person_ready_to_verify_what_to_bring_link_clicked(**extra) # @param [boolean] success sms notification successful or not # @param [String] enrollment_code enrollment_code # @param [String] enrollment_id enrollment_id - # @param [Hash] telephony_response response from Telephony gem + # @param [Hash] telephony_response Response from Telephony gem # @param [Hash] extra extra information def idv_in_person_send_proofing_notification_attempted( success:, @@ -3159,22 +3463,25 @@ def idv_in_person_usps_proofing_results_job_user_sent_to_fraud_review( # @param [String] reason # @param [Integer] enrollment_id # @param [String] exception_class + # @param [String] original_exception_class # @param [String] exception_message def idv_in_person_usps_request_enroll_exception( context:, reason:, enrollment_id:, exception_class:, + original_exception_class:, exception_message:, **extra ) track_event( 'USPS IPPaaS enrollment failed', - context: context, - enrollment_id: enrollment_id, - exception_class: exception_class, - exception_message: exception_message, - reason: reason, + context:, + enrollment_id:, + exception_class:, + original_exception_class:, + exception_message:, + reason:, **extra, ) end @@ -3267,9 +3574,11 @@ def idv_link_sent_capture_doc_polling_started(**extra) end # Tracks when the user visits Mail only warning when vendor_status_sms is set to full_outage - def idv_mail_only_warning_visited(**extra) + # @param [String] analytics_id Current IdV flow identifier + def idv_mail_only_warning_visited(analytics_id:, **extra) track_event( 'IdV: Mail only warning visited', + analytics_id:, **extra, ) end @@ -3415,8 +3724,10 @@ def idv_personal_key_submitted( # @param [Boolean] encrypted_profiles_missing True if user's session had no encrypted pii # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # User visited IDV personal key page def idv_personal_key_visited( + opted_in_to_in_person_proofing: nil, proofing_components: nil, address_verification_method: nil, in_person_verification_pending: nil, @@ -3427,12 +3738,13 @@ def idv_personal_key_visited( ) track_event( 'IdV: personal key visited', - proofing_components: proofing_components, - address_verification_method: address_verification_method, - in_person_verification_pending: in_person_verification_pending, - encrypted_profiles_missing: encrypted_profiles_missing, - active_profile_idv_level: active_profile_idv_level, - pending_profile_idv_level: pending_profile_idv_level, + opted_in_to_in_person_proofing:, + proofing_components:, + address_verification_method:, + in_person_verification_pending:, + encrypted_profiles_missing:, + active_profile_idv_level:, + pending_profile_idv_level:, **extra, ) end @@ -3458,6 +3770,7 @@ def idv_personal_key_visited( # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # The user submitted their phone on the phone confirmation page def idv_phone_confirmation_form_submitted( success:, @@ -3468,6 +3781,7 @@ def idv_phone_confirmation_form_submitted( carrier:, country_code:, area_code:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, error_details: nil, @@ -3486,6 +3800,7 @@ def idv_phone_confirmation_form_submitted( carrier:, country_code:, area_code:, + opted_in_to_in_person_proofing:, skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, otp_delivery_preference:, @@ -3581,7 +3896,7 @@ def idv_phone_confirmation_otp_rate_limit_sends( # @param [String] country_code Abbreviated 2-letter country code associated with phone number # @param [String] area_code area code of phone number # @param [Boolean] rate_limit_exceeded whether or not the rate limit was exceeded by this attempt - # @param [Hash] telephony_response response from Telephony gem + # @param [Hash] telephony_response Response from Telephony gem # @param [String] phone_fingerprint HMAC fingerprint of the phone number formatted as E.164 # @param [Hash,nil] proofing_components User's current proofing components # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID @@ -3638,7 +3953,7 @@ def idv_phone_confirmation_otp_resent( # @param [String] area_code area code of phone number # @param [Boolean] rate_limit_exceeded whether or not the rate limit was exceeded by this attempt # @param [String] phone_fingerprint HMAC fingerprint of the phone number formatted as E.164 - # @param [Hash] telephony_response response from Telephony gem + # @param [Hash] telephony_response Response from Telephony gem # @param [Hash,nil] proofing_components User's current proofing components # @option proofing_components [String,nil] 'document_check' Vendor that verified the user's ID # @option proofing_components [String,nil] 'document_type' Type of ID used to verify @@ -3706,6 +4021,7 @@ def idv_phone_confirmation_otp_sent( # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # When a user attempts to confirm possession of a new phone number during the IDV process def idv_phone_confirmation_otp_submitted( success:, @@ -3715,6 +4031,7 @@ def idv_phone_confirmation_otp_submitted( otp_delivery_preference:, second_factor_attempts_count:, second_factor_locked_at:, + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, error_details: nil, @@ -3733,6 +4050,7 @@ def idv_phone_confirmation_otp_submitted( otp_delivery_preference:, second_factor_attempts_count:, second_factor_locked_at:, + opted_in_to_in_person_proofing:, skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, proofing_components:, @@ -3788,6 +4106,7 @@ def idv_phone_confirmation_otp_visit( # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # The vendor finished the process of confirming the users phone def idv_phone_confirmation_vendor_submitted( success:, @@ -3798,6 +4117,7 @@ def idv_phone_confirmation_vendor_submitted( phone_fingerprint:, new_phone_added:, hybrid_handoff_phone_used:, + opted_in_to_in_person_proofing: nil, error_details: nil, proofing_components: nil, active_profile_idv_level: nil, @@ -3815,6 +4135,7 @@ def idv_phone_confirmation_vendor_submitted( phone_fingerprint:, new_phone_added:, hybrid_handoff_phone_used:, + opted_in_to_in_person_proofing:, proofing_components:, active_profile_idv_level:, pending_profile_idv_level:, @@ -3836,9 +4157,13 @@ def idv_phone_confirmation_vendor_submitted( # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] skip_hybrid_handoff Whether skipped hybrid handoff A/B test is active + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # When a user gets an error during the phone finder flow of IDV def idv_phone_error_visited( type:, + opted_in_to_in_person_proofing: nil, + skip_hybrid_handoff: nil, proofing_components: nil, limiter_expires_at: nil, remaining_submit_attempts: nil, @@ -3849,6 +4174,8 @@ def idv_phone_error_visited( track_event( 'IdV: phone error visited', type:, + opted_in_to_in_person_proofing:, + skip_hybrid_handoff:, proofing_components:, limiter_expires_at:, remaining_submit_attempts:, @@ -3870,8 +4197,10 @@ def idv_phone_error_visited( # @option proofing_components [String,nil] 'threatmetrix_review_status' TMX decision on the user # @param [String,nil] active_profile_idv_level ID verification level of user's active profile. # @param [String,nil] pending_profile_idv_level ID verification level of user's pending profile. + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing # User visited idv phone of record def idv_phone_of_record_visited( + opted_in_to_in_person_proofing: nil, acuant_sdk_upgrade_ab_test_bucket: nil, skip_hybrid_handoff: nil, proofing_components: nil, @@ -3881,6 +4210,7 @@ def idv_phone_of_record_visited( ) track_event( 'IdV: phone of record visited', + opted_in_to_in_person_proofing:, skip_hybrid_handoff:, acuant_sdk_upgrade_ab_test_bucket:, proofing_components:, @@ -4237,16 +4567,17 @@ def idv_selfie_image_clicked( # Tracks when the user visits one of the the session error pages. # @param [String] type - # @param [Integer,nil] submit_attempts_remaining (previously called "attempts_remaining") + # @param [Integer,nil] remaining_submit_attempts + # (previously called "attempts_remaining" and "submit_attempts_remaining") def idv_session_error_visited( type:, - submit_attempts_remaining: nil, + remaining_submit_attempts: nil, **extra ) track_event( 'IdV: session error visited', - type: type, - submit_attempts_remaining: submit_attempts_remaining, + type:, + remaining_submit_attempts:, **extra, ) end @@ -4394,13 +4725,19 @@ def idv_verify_by_mail_enter_code_submitted( # @identity.idp.previous_event_name IdV: GPO verification visited # Visited page used to enter address verification code received via US mail. # @param [String,nil] source The source for the visit (i.e., "gpo_reminder_email"). + # @param [Boolean] otp_rate_limited Whether the user is rate-limited + # @param [Boolean] user_can_request_another_letter Whether user can request another letter def idv_verify_by_mail_enter_code_visited( - source: nil, + source:, + otp_rate_limited:, + user_can_request_another_letter:, **extra ) track_event( 'IdV: enter verify by mail code visited', - source: source, + source:, + otp_rate_limited:, + user_can_request_another_letter:, **extra, ) end @@ -6031,9 +6368,11 @@ def second_mfa_reminder_visit # @param [String] jti # @param [String] user_id # @param [String] client_id + # @param [String] event_type def security_event_received( success:, errors:, + event_type:, error_code: nil, error_details: nil, jti: nil, @@ -6046,6 +6385,7 @@ def security_event_received( success:, errors:, error_details:, + event_type:, error_code:, jti:, user_id:, @@ -6228,7 +6568,7 @@ def sp_select_email_visited(needs_completion_screen_reason: nil, **extra) # @param ["authentication", "reauthentication", "confirmation"] context User session context # @param ["sms", "voice"] otp_delivery_preference Channel used to send the message # @param [Boolean] resend - # @param [Hash] telephony_response + # @param [Hash] telephony_response Response from Telephony gem # @param [:test, :pinpoint] adapter which adapter the OTP was delivered with # @param [Boolean] success # @param [Hash] recaptcha_annotation Details of reCAPTCHA annotation, if submitted @@ -6598,19 +6938,28 @@ def user_suspension_confirmed # @param [Integer] enrollment_id # @param [Boolean] second_address_line_present # @param [String] service_provider + # @param [Boolean] opted_in_to_in_person_proofing User opted into in person proofing + # @param [String] tmx_status the tmx_status of the enrollment profile profile + # @param [Boolean] enhanced_ipp Whether enrollment is for enhanced in-person proofing def usps_ippaas_enrollment_created( enrollment_code:, enrollment_id:, second_address_line_present:, service_provider:, + opted_in_to_in_person_proofing:, + tmx_status:, + enhanced_ipp:, **extra ) track_event( 'USPS IPPaaS enrollment created', - enrollment_code: enrollment_code, - enrollment_id: enrollment_id, - second_address_line_present: second_address_line_present, - service_provider: service_provider, + enrollment_code:, + enrollment_id:, + second_address_line_present:, + service_provider:, + opted_in_to_in_person_proofing:, + tmx_status:, + enhanced_ipp:, **extra, ) end diff --git a/app/services/authn_context_resolver.rb b/app/services/authn_context_resolver.rb index 20accf95380..e50fb24268f 100644 --- a/app/services/authn_context_resolver.rb +++ b/app/services/authn_context_resolver.rb @@ -19,15 +19,16 @@ def result end def asserted_ial_acr - return Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF unless user.active_profile.present? + return resolve_acr(Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF) unless + user&.identity_verified? if result.biometric_comparison? - Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF + resolve_acr(Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF) elsif result.identity_proofing? || result.ialmax? - Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF + resolve_acr(Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF) else - Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF + resolve_acr(Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF) end end @@ -113,7 +114,7 @@ def decorate_acr_result_with_user_context(result) def result_with_sp_ial_defaults(result) if acr_ial_component_values.any? result - elsif service_provider&.ial.to_i >= 2 + elsif service_provider&.identity_proofing_allowed? result.with(identity_proofing?: true, aal2?: true) else result @@ -129,14 +130,21 @@ def acr_aal_component_values def acr_ial_component_values acr_result_without_sp_defaults.component_values.filter do |component_value| - component_value.name.include?('ial') || component_value.name.include?('loa') + Saml::Idp::Constants::AUTHN_CONTEXT_CLASSREF_TO_IAL.include?(component_value.name) end end + def resolve_acr(acr) + return acr unless use_semantic_authn_contexts? + Saml::Idp::Constants::LEGACY_ACRS_TO_SEMANTIC_ACRS.fetch(acr, default_value: acr) + end + def biometric_is_required?(result) - result. - component_values. - map(&:name). - include?(Saml::Idp::Constants::IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF) + Saml::Idp::Constants::BIOMETRIC_REQUIRED_IAL_CONTEXTS.intersect?(result.component_names) + end + + def use_semantic_authn_contexts? + @use_semantic_authn_contexts ||= service_provider&.semantic_authn_contexts_allowed? && + Vot::AcrComponentValues.any_semantic_acrs?(acr_values) end end diff --git a/app/services/doc_auth/mock/result_response.rb b/app/services/doc_auth/mock/result_response.rb index 3d7784016a6..36391ed1200 100644 --- a/app/services/doc_auth/mock/result_response.rb +++ b/app/services/doc_auth/mock/result_response.rb @@ -28,7 +28,8 @@ def initialize(uploaded_file, config, selfie_required = false) classification_info: classification_info, workflow: workflow, liveness_checking_required: @selfie_required, - }.compact, + **@response_info.to_h, + }, ) end @@ -44,7 +45,7 @@ def errors failed = file_data.dig('failed_alerts')&.dup passed = file_data.dig('passed_alerts') face_match_result = file_data.dig('portrait_match_results', 'FaceMatchResult') - classification_info = file_data.dig('classification_info') + classification_info = file_data.dig('classification_info')&.symbolize_keys # Pass and doc type is ok has_fields = [ doc_auth_result, @@ -69,8 +70,8 @@ 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? - fake_response_info = create_response_info(**mock_args) - ErrorGenerator.new(config).generate_doc_auth_errors(fake_response_info) + @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 file_data end @@ -232,8 +233,7 @@ def create_response_info( liveness_enabled: liveness_enabled, classification_info: classification_info, portrait_match_results: selfie_check_performed? ? portrait_match_results : nil, - extra: { liveness_checking_required: liveness_enabled }, - }.compact + } end end end diff --git a/app/services/flow/flow_state_machine.rb b/app/services/flow/flow_state_machine.rb index c2add6ff2e1..02ca54fdcc3 100644 --- a/app/services/flow/flow_state_machine.rb +++ b/app/services/flow/flow_state_machine.rb @@ -28,7 +28,6 @@ def update result = flow.handle(step) - increment_step_name_counts analytics.public_send( flow.step_handler_instance(step).analytics_submitted_event, **result.to_h.merge(analytics_properties), @@ -54,7 +53,6 @@ def current_step end def track_step_visited - increment_step_name_counts analytics.public_send( flow.step_handler(current_step).analytics_visited_event, **analytics_properties, @@ -188,7 +186,6 @@ def analytics_properties { flow_path: flow.flow_path, step: current_step, - step_count: current_flow_step_counts[current_step_name], analytics_id: @analytics_id, }.merge(flow.extra_analytics_properties). merge(**opt_in_analytics_properties) @@ -198,16 +195,6 @@ def current_step_name "#{current_step}_#{action_name}" end - def current_flow_step_counts - current_session["#{@name}_flow_step_counts"] ||= {} - current_session["#{@name}_flow_step_counts"].default = 0 - current_session["#{@name}_flow_step_counts"] - end - - def increment_step_name_counts - current_flow_step_counts[current_step_name] += 1 - end - def next_step flow.next_step end diff --git a/app/services/saml_request_validator.rb b/app/services/saml_request_validator.rb index dd62af36cdd..7b072c5e525 100644 --- a/app/services/saml_request_validator.rb +++ b/app/services/saml_request_validator.rb @@ -59,10 +59,10 @@ def authorized_service_provider def authorized_authn_context if !valid_authn_context? || - (identity_proofing_requested? && service_provider&.ial != 2) || - (ial_max_requested? && - !IdentityConfig.store.allowed_ialmax_providers.include?(service_provider&.issuer)) || - (biometric_ial_requested? && !service_provider.biometric_ial_allowed?) + (identity_proofing_requested? && !service_provider.identity_proofing_allowed?) || + (ial_max_requested? && !service_provider.ialmax_allowed?) || + (biometric_ial_requested? && !service_provider.biometric_ial_allowed?) || + (semantic_authn_contexts_requested? && !service_provider.semantic_authn_contexts_allowed?) errors.add(:authn_context, :unauthorized_authn_context, type: :unauthorized_authn_context) end end @@ -120,6 +120,11 @@ def biometric_ial_requested? Array(authn_context).any? { |ial| Saml::Idp::Constants::BIOMETRIC_IAL_CONTEXTS.include? ial } end + def semantic_authn_contexts_requested? + return false if vtr.present? || parsed_vectors_of_trust.present? + Saml::Idp::Constants::SEMANTIC_ACRS.intersect?(authn_context) + end + def authorized_email_nameid_format return unless email_nameid_format? return if service_provider&.email_nameid_format_allowed diff --git a/app/services/service_provider_request_handler.rb b/app/services/service_provider_request_handler.rb index d549ce29f30..4fd4144bc77 100644 --- a/app/services/service_provider_request_handler.rb +++ b/app/services/service_provider_request_handler.rb @@ -28,9 +28,7 @@ def call attr_reader :url, :session, :protocol_request, :protocol def ial - uri = URI.parse(protocol.ial) - ial_url = "#{uri.scheme}://#{uri.hostname}#{uri.path}" - Saml::Idp::Constants::IAL2_AUTHN_CONTEXTS.include?(ial_url) ? 2 : 1 + Saml::Idp::Constants::IAL2_AUTHN_CONTEXTS.include?(protocol.ial) ? 2 : 1 end def current_sp diff --git a/app/services/user_alerts/alert_user_about_account_verified.rb b/app/services/user_alerts/alert_user_about_account_verified.rb index 4ad165888d4..cc6e5c62ca6 100644 --- a/app/services/user_alerts/alert_user_about_account_verified.rb +++ b/app/services/user_alerts/alert_user_about_account_verified.rb @@ -2,11 +2,12 @@ module UserAlerts class AlertUserAboutAccountVerified - def self.call(user:, date_time:, sp_name:) - sp_name ||= APP_NAME + def self.call(profile:) + user = profile.user + sp_name = profile.initiating_service_provider&.friendly_name || APP_NAME user.confirmed_email_addresses.each do |email_address| UserMailer.with(user: user, email_address: email_address).account_verified( - date_time: date_time, + date_time: profile.verified_at, sp_name: sp_name, ).deliver_now_or_later end diff --git a/app/services/vot/acr_component_values.rb b/app/services/vot/acr_component_values.rb index dba2f03317d..a10ebee50e6 100644 --- a/app/services/vot/acr_component_values.rb +++ b/app/services/vot/acr_component_values.rb @@ -49,6 +49,33 @@ module AcrComponentValues requirements: [:aal2, :ialmax], ).freeze + IAL_AUTH_ONLY = ComponentValue.new( + name: Saml::Idp::Constants::IAL_AUTH_ONLY_ACR, + description: 'IAL1 - no identity proofing (NIST SP 800-63-3)', + implied_component_values: [], + requirements: [], + ).freeze + IAL_VERIFIED = ComponentValue.new( + name: Saml::Idp::Constants::IAL_VERIFIED_ACR, + description: 'IAL2 - basic identity proofing, no biometrics (NIST SP 800-63-3)', + implied_component_values: [], + requirements: [:aal2, :identity_proofing], + ).freeze + IAL_VERIFIED_FACIAL_MATCH_PREFERRED = ComponentValue.new( + name: Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR, + description: 'IAL2 - biometric-verified identity used if available (NIST SP 800-63-3)', + implied_component_values: [], + requirements: [:aal2, :identity_proofing, :biometric_comparison, + :two_pieces_of_fair_evidence], + ).freeze + IAL_VERIFIED_FACIAL_MATCH_REQUIRED = ComponentValue.new( + name: Saml::Idp::Constants::IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR, + description: 'IAL2 - require identity-proofing using facial match (NIST SP 800-63-3)', + implied_component_values: [], + requirements: [:aal2, :identity_proofing, :biometric_comparison, + :two_pieces_of_fair_evidence], + ).freeze + ## Authentication ACR values DEFAULT = ComponentValue.new( name: Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, @@ -98,8 +125,22 @@ module AcrComponentValues [component_value.name, component_value] end.to_h.freeze + DELIM = ' ' + def self.by_name NAME_HASH end + + # @param acr_values [String,Array] + def self.any_semantic_acrs?(acr_values) + return false unless acr_values.present? + # @type [Array] + values = ( + acr_values.is_a?(String) && acr_values.split(DELIM) || + (acr_values.is_a?(Array) || acr_values.is_a?(Set)) && acr_values || + [acr_values].compact + ).to_a + Saml::Idp::Constants::SEMANTIC_ACRS.intersect?(values) + end end end diff --git a/app/services/vot/parser.rb b/app/services/vot/parser.rb index 1120737035e..94e54c2b4ef 100644 --- a/app/services/vot/parser.rb +++ b/app/services/vot/parser.rb @@ -4,6 +4,10 @@ module Vot class Parser class ParseException < StandardError; end + class UnsupportedComponentsException < ParseException; end + + class DuplicateComponentsException < ParseException; end + Result = Data.define( :component_values, :component_separator, @@ -38,6 +42,10 @@ def identity_proofing_or_ialmax? def expanded_component_values component_values.map(&:name).join(component_separator) end + + def component_names + component_values.map(&:name) + end end.freeze attr_reader :vector_of_trust, :acr_values @@ -107,17 +115,19 @@ def validate_component_uniqueness!(component_values) def raise_unsupported_component_exception(component_value_name) if vector_of_trust.present? - raise ParseException, "#{vector_of_trust} contains unkown component #{component_value_name}" + raise UnsupportedComponentsException, + "'#{vector_of_trust}' contains unknown component '#{component_value_name}'" else - raise ParseException, "#{acr_values} contains unkown acr value #{component_value_name}" + raise UnsupportedComponentsException, + "'#{acr_values}' contains unknown acr value '#{component_value_name}'" end end def raise_duplicate_component_exception if vector_of_trust.present? - raise ParseException, "#{vector_of_trust} contains duplicate components" + raise DuplicateComponentsException, "'#{vector_of_trust}' contains duplicate components" else - raise ParseException, "#{acr_values} ontains duplicate acr values" + raise DuplicateComponentsException, "'#{acr_values}' contains duplicate acr values" end end end diff --git a/config/application.yml.default b/config/application.yml.default index 48113c3296f..9a3d1aea47e 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -32,6 +32,7 @@ address_identity_proofing_supported_country_codes: '["AS", "GU", "MP", "PR", "US all_redirect_uris_cache_duration_minutes: 2 allowed_biometric_ial_providers: '[]' allowed_ialmax_providers: '[]' +allowed_valid_authn_contexts_semantic_providers: '[]' allowed_verified_within_providers: '[]' asset_host: '' async_stale_job_timeout_seconds: 300 @@ -119,6 +120,7 @@ event_disavowal_expiration_hours: 240 feature_idv_force_gpo_verification_enabled: false feature_idv_hybrid_flow_enabled: true feature_select_email_to_share_enabled: true +feature_valid_authn_contexts_semantic_enabled: true geo_data_file_path: 'geo_data/GeoLite2-City.mmdb' get_usps_proofing_results_job_cron: '0/30 * * * *' get_usps_proofing_results_job_reprocess_delay_minutes: 5 @@ -376,6 +378,7 @@ usps_mock_fallback: true usps_upload_enabled: false usps_upload_sftp_timeout: 5 valid_authn_contexts: '["http://idmanagement.gov/ns/assurance/loa/1", "http://idmanagement.gov/ns/assurance/loa/3", "http://idmanagement.gov/ns/assurance/ial/1", "http://idmanagement.gov/ns/assurance/ial/2", "http://idmanagement.gov/ns/assurance/ial/0", "http://idmanagement.gov/ns/assurance/ial/2?strict=true", "http://idmanagement.gov/ns/assurance/ial/2?bio=preferred", "http://idmanagement.gov/ns/assurance/ial/2?bio=required", "urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo", "http://idmanagement.gov/ns/assurance/aal/2", "http://idmanagement.gov/ns/assurance/aal/3", "http://idmanagement.gov/ns/assurance/aal/3?hspd12=true","http://idmanagement.gov/ns/assurance/aal/2?phishing_resistant=true","http://idmanagement.gov/ns/assurance/aal/2?hspd12=true"]' +valid_authn_contexts_semantic: '["http://idmanagement.gov/ns/assurance/loa/1", "http://idmanagement.gov/ns/assurance/loa/3", "http://idmanagement.gov/ns/assurance/ial/1", "http://idmanagement.gov/ns/assurance/ial/2", "http://idmanagement.gov/ns/assurance/ial/0", "http://idmanagement.gov/ns/assurance/ial/2?strict=true", "http://idmanagement.gov/ns/assurance/ial/2?bio=preferred", "http://idmanagement.gov/ns/assurance/ial/2?bio=required", "urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo", "http://idmanagement.gov/ns/assurance/aal/2", "http://idmanagement.gov/ns/assurance/aal/3", "http://idmanagement.gov/ns/assurance/aal/3?hspd12=true","http://idmanagement.gov/ns/assurance/aal/2?phishing_resistant=true","http://idmanagement.gov/ns/assurance/aal/2?hspd12=true", "urn:acr.login.gov:auth-only", "urn:acr.login.gov:verified","urn:acr.login.gov:verified-facial-match-preferred","urn:acr.login.gov:verified-facial-match-required"]' vendor_status_idv_scheduled_maintenance_finish: '' vendor_status_idv_scheduled_maintenance_start: '' vendor_status_lexisnexis_instant_verify: 'operational' @@ -471,6 +474,7 @@ production: enable_test_routes: false enable_usps_verification: false feature_select_email_to_share_enabled: false + feature_valid_authn_contexts_semantic_enabled: false hmac_fingerprinter_key: hmac_fingerprinter_key_queue: '[]' idv_sp_required: true diff --git a/config/initializers/job_configurations.rb b/config/initializers/job_configurations.rb index 2b5f2c7c323..9d74b63cecd 100644 --- a/config/initializers/job_configurations.rb +++ b/config/initializers/job_configurations.rb @@ -5,7 +5,6 @@ cron_1h = '0 * * * *' cron_24h = '0 0 * * *' cron_24h_and_a_bit = '12 0 * * *' # 0000 UTC + 12 min, staggered from whatever else runs at 0000 UTC -cron_24h_1am = '0 1 * * *' # 1am UTC is 8pm EST/9pm EDT gpo_cron_24h = '0 10 * * *' # 10am UTC is 5am EST/6am EDT cron_every_monday = 'every Monday at 0:25 UTC' # equivalent to '25 0 * * 1' cron_every_monday_1am = 'every Monday at 1:00 UTC' # equivalent to '0 1 * * 1' @@ -54,14 +53,9 @@ args: -> { [Time.zone.today] }, }, # Combined Invoice Supplement Report to S3 - combined_invoice_supplement_report: { - class: 'Reports::CombinedInvoiceSupplementReport', - cron: cron_24h, - args: -> { [Time.zone.today] }, - }, combined_invoice_supplement_report_v2: { class: 'Reports::CombinedInvoiceSupplementReportV2', - cron: cron_24h_1am, + cron: cron_24h, args: -> { [Time.zone.today] }, }, agreement_summary_report: { diff --git a/lib/action_account.rb b/lib/action_account.rb index 2ef444c7d9b..cc6c1982f87 100644 --- a/lib/action_account.rb +++ b/lib/action_account.rb @@ -254,14 +254,6 @@ def run(args:, config:) class ReviewPass include LogBase - def alert_verified(user:, date_time:) - UserAlerts::AlertUserAboutAccountVerified.call( - user: user, - date_time: date_time, - sp_name: nil, - ) - end - def run(args:, config:) uuids = args @@ -286,10 +278,9 @@ def run(args:, config:) success = true if profile.active? - event, _disavowal_token = UserEventCreator.new(current_user: user). + UserEventCreator.new(current_user: user). create_out_of_band_user_event(:account_verified) - - alert_verified(user: user, date_time: event.created_at) + UserAlerts::AlertUserAboutAccountVerified.call(profile: profile) log_texts << log_text[:profile_activated] else diff --git a/lib/feature_management.rb b/lib/feature_management.rb index c5469bb8dc5..3642ff1404f 100644 --- a/lib/feature_management.rb +++ b/lib/feature_management.rb @@ -159,4 +159,10 @@ def self.idv_by_mail_only? outage_status.any_phone_vendor_outage? || outage_status.phone_finder_outage? end + + # Whether to use the valid Authn Context Classrefs that include + # the newest ACR values + def self.use_semantic_authn_contexts? + IdentityConfig.store.dig(:feature_valid_authn_contexts_semantic_enabled) ? true : false + end end diff --git a/lib/identity_config.rb b/lib/identity_config.rb index a4b83521aa2..8f9c0909de1 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -47,6 +47,7 @@ def self.store config.add(:all_redirect_uris_cache_duration_minutes, type: :integer) config.add(:allowed_biometric_ial_providers, type: :json) config.add(:allowed_ialmax_providers, type: :json) + config.add(:allowed_valid_authn_contexts_semantic_providers, type: :json) config.add(:allowed_verified_within_providers, type: :json) config.add(:asset_host, type: :string) config.add(:async_stale_job_timeout_seconds, type: :integer) @@ -139,6 +140,7 @@ def self.store config.add(:feature_idv_force_gpo_verification_enabled, type: :boolean) config.add(:feature_idv_hybrid_flow_enabled, type: :boolean) config.add(:feature_select_email_to_share_enabled, type: :boolean) + config.add(:feature_valid_authn_contexts_semantic_enabled, type: :boolean) config.add(:geo_data_file_path, type: :string) config.add(:get_usps_proofing_results_job_cron, type: :string) config.add(:get_usps_proofing_results_job_reprocess_delay_minutes, type: :integer) @@ -429,6 +431,7 @@ def self.store config.add(:usps_upload_sftp_timeout, type: :integer) config.add(:usps_upload_sftp_username, type: :string) config.add(:valid_authn_contexts, type: :json) + config.add(:valid_authn_contexts_semantic, type: :json) config.add(:vendor_status_lexisnexis_instant_verify, type: :symbol, enum: VENDOR_STATUS_OPTIONS) config.add(:vendor_status_lexisnexis_phone_finder, type: :symbol, enum: VENDOR_STATUS_OPTIONS) config.add(:vendor_status_lexisnexis_trueid, type: :symbol, enum: VENDOR_STATUS_OPTIONS) diff --git a/lib/reporting/identity_verification_report.rb b/lib/reporting/identity_verification_report.rb index 24951001dfe..8ee8fbb3e77 100644 --- a/lib/reporting/identity_verification_report.rb +++ b/lib/reporting/identity_verification_report.rb @@ -353,7 +353,7 @@ def query | filter (name = %{fraud_review_passed} and properties.event_properties.success = 1) or (name != %{fraud_review_passed}) | fields - coalesce(properties.event_properties.fraud_review_pending, 0) AS fraud_review_pending + coalesce(properties.event_properties.fraud_review_pending, properties.event_properties.fraud_pending_reason, 0) AS fraud_review_pending , coalesce(properties.event_properties.gpo_verification_pending, 0) AS gpo_verification_pending , coalesce(properties.event_properties.in_person_verification_pending, 0) AS in_person_verification_pending , ispresent(properties.event_properties.deactivation_reason) AS has_other_deactivation_reason diff --git a/lib/saml_idp_constants.rb b/lib/saml_idp_constants.rb index 1ce6ddec407..d8f4902fcd5 100644 --- a/lib/saml_idp_constants.rb +++ b/lib/saml_idp_constants.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'idp/constants' - # rubocop:disable Layout/LineLength # Global constants used by the SAML IdP module Saml @@ -17,10 +16,18 @@ module Constants IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF = "#{IAL_AUTHN_CONTEXT_PREFIX}/2?bio=preferred".freeze IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF = "#{IAL_AUTHN_CONTEXT_PREFIX}/2?bio=required".freeze + ACR_URN_NID = 'acr.login.gov' + ACR_URN_PREFIX = "urn:#{ACR_URN_NID}".freeze + IAL_AUTH_ONLY_ACR = "#{ACR_URN_PREFIX}:auth-only".freeze + IAL_VERIFIED_ACR = "#{ACR_URN_PREFIX}:verified".freeze + IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR = "#{ACR_URN_PREFIX}:verified-facial-match-required".freeze + IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR = "#{ACR_URN_PREFIX}:verified-facial-match-preferred".freeze + PASSWORD_AUTHN_CONTEXT_CLASSREFS = %w[ urn:oasis:names:tc:SAML:2.0:ac:classes:Password urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport ].freeze + DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF = 'urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo' AAL_AUTHN_CONTEXT_PREFIX = 'http://idmanagement.gov/ns/assurance/aal' AAL1_AUTHN_CONTEXT_CLASSREF = "#{AAL_AUTHN_CONTEXT_PREFIX}/1".freeze @@ -37,10 +44,30 @@ module Constants REQUESTED_ATTRIBUTES_CLASSREF = 'http://idmanagement.gov/ns/requested_attributes?ReqAttr=' - VALID_AUTHN_CONTEXTS = IdentityConfig.store.valid_authn_contexts.freeze - IAL2_AUTHN_CONTEXTS = [IAL2_AUTHN_CONTEXT_CLASSREF, LOA3_AUTHN_CONTEXT_CLASSREF].freeze - BIOMETRIC_IAL_CONTEXTS = [IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF, - IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF].freeze + VALID_AUTHN_CONTEXTS = (if FeatureManagement.use_semantic_authn_contexts? + IdentityConfig.store.valid_authn_contexts_semantic + else + IdentityConfig.store.valid_authn_contexts + end).freeze + + BIOMETRIC_IAL_CONTEXTS = [ + IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR, + IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR, + IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF, + IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF, + ].freeze + + BIOMETRIC_REQUIRED_IAL_CONTEXTS = [ + IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR, + IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF, + ].freeze + + IAL2_AUTHN_CONTEXTS = [ + *BIOMETRIC_IAL_CONTEXTS, + IAL_VERIFIED_ACR, + IAL2_AUTHN_CONTEXT_CLASSREF, + LOA3_AUTHN_CONTEXT_CLASSREF, + ].freeze AUTHN_CONTEXT_CLASSREF_TO_IAL = { LOA1_AUTHN_CONTEXT_CLASSREF => ::Idp::Constants::IAL1, @@ -50,6 +77,10 @@ module Constants IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF => ::Idp::Constants::IAL2, IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF => ::Idp::Constants::IAL2, IALMAX_AUTHN_CONTEXT_CLASSREF => ::Idp::Constants::IAL_MAX, + IAL_AUTH_ONLY_ACR => ::Idp::Constants::IAL1, + IAL_VERIFIED_ACR => ::Idp::Constants::IAL2, + IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR => ::Idp::Constants::IAL2, + IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR => ::Idp::Constants::IAL2, }.freeze AUTHN_CONTEXT_IAL_TO_CLASSREF = { @@ -74,6 +105,22 @@ module Constants ::Idp::Constants::AAL2 => AAL2_AUTHN_CONTEXT_CLASSREF, ::Idp::Constants::AAL3 => AAL3_AUTHN_CONTEXT_CLASSREF, }.freeze + + SEMANTIC_ACRS = [ + IAL_AUTH_ONLY_ACR, + IAL_VERIFIED_ACR, + IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR, + IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR, + ].freeze + + LEGACY_ACRS_TO_SEMANTIC_ACRS = { + LOA1_AUTHN_CONTEXT_CLASSREF => IAL_AUTH_ONLY_ACR, + LOA3_AUTHN_CONTEXT_CLASSREF => IAL_VERIFIED_ACR, + IAL1_AUTHN_CONTEXT_CLASSREF => IAL_AUTH_ONLY_ACR, + IAL2_AUTHN_CONTEXT_CLASSREF => IAL_VERIFIED_ACR, + IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF => IAL_VERIFIED_FACIAL_MATCH_PREFERRED_ACR, + IAL2_BIO_REQUIRED_AUTHN_CONTEXT_CLASSREF => IAL_VERIFIED_FACIAL_MATCH_REQUIRED_ACR, + }.freeze end end end diff --git a/spec/controllers/idv/agreement_controller_spec.rb b/spec/controllers/idv/agreement_controller_spec.rb index 1405003e49a..d4905310069 100644 --- a/spec/controllers/idv/agreement_controller_spec.rb +++ b/spec/controllers/idv/agreement_controller_spec.rb @@ -1,19 +1,14 @@ require 'rails_helper' -RSpec.describe Idv::AgreementController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::AgreementController do include FlowPolicyHelper let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_sign_in(user) stub_up_to(:welcome, idv_session: subject.idv_session) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#step_info' do @@ -44,7 +39,7 @@ { step: 'agreement', analytics_id: 'Doc Auth', - }.merge(ab_test_args) + } end it 'renders the show template' do @@ -108,7 +103,7 @@ errors: {}, step: 'agreement', analytics_id: 'Doc Auth', - }.merge(ab_test_args) + } end let(:skip_hybrid_handoff) { nil } diff --git a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb index d3d74ceeae8..97473107412 100644 --- a/spec/controllers/idv/by_mail/enter_code_controller_spec.rb +++ b/spec/controllers/idv/by_mail/enter_code_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::ByMail::EnterCodeController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::ByMail::EnterCodeController do let(:good_otp) { 'ABCDE12345' } let(:bad_otp) { 'bad-otp' } let(:threatmetrix_enabled) { false } @@ -215,7 +215,9 @@ it 'dispatches account verified alert' do action - expect(UserAlerts::AlertUserAboutAccountVerified).to have_received(:call) + expect(UserAlerts::AlertUserAboutAccountVerified).to have_received(:call).with( + profile: user.active_profile, + ) end context 'with establishing in person enrollment' do diff --git a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb index 357fa45596d..81e58922453 100644 --- a/spec/controllers/idv/by_mail/request_letter_controller_spec.rb +++ b/spec/controllers/idv/by_mail/request_letter_controller_spec.rb @@ -1,16 +1,10 @@ require 'rails_helper' -RSpec.describe Idv::ByMail::RequestLetterController, - allowed_extra_analytics: [:sample_bucket1, :sample_bucket2] do +RSpec.describe Idv::ByMail::RequestLetterController do let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#step_info' do @@ -104,7 +98,6 @@ resend: false, phone_step_attempts: 1, hours_since_first_letter: 0, - **ab_test_args, ), ) end diff --git a/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb b/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb index 79a91b15d64..5ca562aa876 100644 --- a/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb +++ b/spec/controllers/idv/by_mail/resend_letter_controller_spec.rb @@ -1,17 +1,11 @@ require 'rails_helper' -RSpec.describe Idv::ByMail::ResendLetterController, - allowed_extra_analytics: [:sample_bucket1, :sample_bucket2] do +RSpec.describe Idv::ByMail::ResendLetterController do let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_sign_in(user) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#new' do diff --git a/spec/controllers/idv/document_capture_controller_spec.rb b/spec/controllers/idv/document_capture_controller_spec.rb index d733c830c2f..41529df2ddb 100644 --- a/spec/controllers/idv/document_capture_controller_spec.rb +++ b/spec/controllers/idv/document_capture_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::DocumentCaptureController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::DocumentCaptureController do include FlowPolicyHelper let(:document_capture_session_requested_at) { Time.zone.now } @@ -16,10 +16,6 @@ let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - # selfie related test flags let(:sp_selfie_enabled) { false } let(:flow_path) { 'standard' } @@ -36,7 +32,6 @@ allow(controller).to receive(:resolved_authn_context_result). and_return(resolved_authn_context) subject.idv_session.flow_path = flow_path - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#step_info' do @@ -109,7 +104,7 @@ step: 'document_capture', liveness_checking_required: false, selfie_check_required: sp_selfie_enabled, - }.merge(ab_test_args) + } end it 'has non-nil presenter' do @@ -301,7 +296,7 @@ step: 'document_capture', liveness_checking_required: false, selfie_check_required: sp_selfie_enabled, - }.merge(ab_test_args) + } end let(:result) { { success: true, errors: {} } } diff --git a/spec/controllers/idv/enter_password_controller_spec.rb b/spec/controllers/idv/enter_password_controller_spec.rb index 0a17501585d..97c6e7f4bc3 100644 --- a/spec/controllers/idv/enter_password_controller_spec.rb +++ b/spec/controllers/idv/enter_password_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::EnterPasswordController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::EnterPasswordController do include UspsIppHelper let(:user) do @@ -17,15 +17,10 @@ subject.idv_session end - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_analytics stub_sign_in(user) allow(IdentityConfig.store).to receive(:usps_mock_fallback).and_return(false) - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) subject.idv_session.welcome_visited = true subject.idv_session.idv_consent_given_at = Time.zone.now subject.idv_session.proofing_started_at = 5.minutes.ago.iso8601 @@ -278,7 +273,6 @@ def show fraud_rejection: false, gpo_verification_pending: false, in_person_verification_pending: false, - **ab_test_args, ), ) end @@ -296,7 +290,6 @@ def show gpo_verification_pending: false, in_person_verification_pending: false, proofing_workflow_time_in_seconds: 5.minutes.to_i, - **ab_test_args, ), ) expect(@analytics).to have_logged_event( @@ -394,9 +387,13 @@ def show end it 'dispatches account verified alert' do - expect(UserAlerts::AlertUserAboutAccountVerified).to receive(:call) + allow(UserAlerts::AlertUserAboutAccountVerified).to receive(:call) put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } + + expect(UserAlerts::AlertUserAboutAccountVerified).to have_received(:call).with( + profile: user.reload.active_profile, + ) end it 'creates an `account_verified` event once per confirmation' do @@ -818,6 +815,11 @@ def show !review_status.nil? && review_status != 'pass' end let(:review_status) { review_status } + let(:fraud_pending_reason) do + if fraud_review_pending? + "threatmetrix_#{review_status}" + end + end let(:proofing_device_profiling_state) { proofing_device_profiling_state } before do @@ -838,23 +840,27 @@ def show expect(@analytics).to have_logged_event( :idv_enter_password_submitted, hash_including( - success: true, - fraud_review_pending: fraud_review_pending?, - fraud_rejection: false, - gpo_verification_pending: false, - in_person_verification_pending: false, - **ab_test_args, + { + success: true, + fraud_review_pending: fraud_review_pending?, + fraud_pending_reason: fraud_pending_reason, + fraud_rejection: false, + gpo_verification_pending: false, + in_person_verification_pending: false, + }.compact, ), ) expect(@analytics).to have_logged_event( 'IdV: final resolution', hash_including( - success: true, - fraud_review_pending: fraud_review_pending?, - fraud_rejection: false, - gpo_verification_pending: false, - in_person_verification_pending: false, - **ab_test_args, + { + success: true, + fraud_review_pending: fraud_review_pending?, + fraud_pending_reason: fraud_pending_reason, + fraud_rejection: false, + gpo_verification_pending: false, + in_person_verification_pending: false, + }.compact, ), ) end @@ -911,7 +917,6 @@ def show phone_step_attempts: 1, first_letter_requested_at: subject.idv_session.profile.gpo_verification_pending_at, hours_since_first_letter: 0, - **ab_test_args, ), ) end @@ -931,7 +936,6 @@ def show phone_step_attempts: RateLimiter.max_attempts(rate_limit_type), first_letter_requested_at: subject.idv_session.profile.gpo_verification_pending_at, hours_since_first_letter: 0, - **ab_test_args, ), ) end diff --git a/spec/controllers/idv/how_to_verify_controller_spec.rb b/spec/controllers/idv/how_to_verify_controller_spec.rb index 533531bd55e..79a87fd0123 100644 --- a/spec/controllers/idv/how_to_verify_controller_spec.rb +++ b/spec/controllers/idv/how_to_verify_controller_spec.rb @@ -1,11 +1,8 @@ require 'rails_helper' -RSpec.describe Idv::HowToVerifyController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::HowToVerifyController do let(:user) { create(:user) } let(:enabled) { true } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end let(:service_provider) do create(:service_provider, :active, :in_person_proofing_enabled) end @@ -15,7 +12,6 @@ allow(IdentityConfig.store).to receive(:in_person_proofing_enabled) { true } stub_sign_in(user) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) allow(subject.idv_session).to receive(:service_provider).and_return(service_provider) subject.idv_session.welcome_visited = true subject.idv_session.idv_consent_given_at = Time.zone.now @@ -114,7 +110,7 @@ { step: 'how_to_verify', analytics_id: 'Doc Auth', - }.merge(ab_test_args) + } end it 'renders the show template' do @@ -183,7 +179,7 @@ error_details: { selection: { blank: true } }, errors: { selection: ['Select a way to verify your identity.'] }, success: false, - }.merge(ab_test_args) + } end let(:params) { nil } @@ -199,11 +195,11 @@ { step: 'how_to_verify', analytics_id: 'Doc Auth', - 'selection' => selection, + selection:, error_details: { selection: { inclusion: true } }, errors: { selection: ['Select a way to verify your identity.'] }, success: false, - }.merge(ab_test_args) + } end it_behaves_like 'invalid form submissions' @@ -217,8 +213,8 @@ step: 'how_to_verify', errors: {}, success: true, - 'selection' => selection, - }.merge(ab_test_args) + selection:, + } end it 'sets skip doc auth on idv session to false and redirects to hybrid handoff' do put :update, params: params @@ -243,8 +239,8 @@ step: 'how_to_verify', errors: {}, success: true, - 'selection' => selection, - }.merge(ab_test_args) + selection:, + } end it 'sets skip doc auth on idv session to true and redirects to document capture' do put :update, params: params diff --git a/spec/controllers/idv/hybrid_handoff_controller_spec.rb b/spec/controllers/idv/hybrid_handoff_controller_spec.rb index dcde07b2842..65d5c6c9300 100644 --- a/spec/controllers/idv/hybrid_handoff_controller_spec.rb +++ b/spec/controllers/idv/hybrid_handoff_controller_spec.rb @@ -1,13 +1,10 @@ require 'rails_helper' -RSpec.describe Idv::HybridHandoffController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::HybridHandoffController do include FlowPolicyHelper let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end let(:service_provider) do create(:service_provider, :active, :in_person_proofing_enabled) end @@ -21,7 +18,6 @@ stub_sign_in(user) stub_up_to(:agreement, idv_session: subject.idv_session) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) allow(subject.idv_session).to receive(:service_provider).and_return(service_provider) resolved_authn_context_result = sp_selfie_enabled ? @@ -66,7 +62,7 @@ step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: sp_selfie_enabled, - }.merge(ab_test_args) + } end it 'renders the show template' do @@ -305,7 +301,7 @@ request_id: 'fake-message-request-id', success: true, }, - }.merge(ab_test_args) + } end let(:params) do @@ -355,7 +351,7 @@ step: 'hybrid_handoff', analytics_id: 'Doc Auth', selfie_check_required: sp_selfie_enabled, - }.merge(ab_test_args) + } end let(:params) do diff --git a/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb index df19af2a7ef..92cd64ad58f 100644 --- a/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/capture_complete_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::HybridMobile::CaptureCompleteController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::HybridMobile::CaptureCompleteController do let(:user) { create(:user) } let!(:document_capture_session) do @@ -20,17 +20,12 @@ ) end - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do session[:doc_capture_user_id] = user&.id session[:document_capture_session_uuid] = document_capture_session_uuid stub_analytics allow(subject).to receive(:confirm_document_capture_session_complete). and_return(true) - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe 'before_actions' do @@ -50,7 +45,7 @@ flow_path: 'hybrid', step: 'capture_complete', liveness_checking_required: false, - }.merge(ab_test_args) + } end it 'renders the show template' do diff --git a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb index d7cefb2138d..3f58041aa49 100644 --- a/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/document_capture_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::HybridMobile::DocumentCaptureController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::HybridMobile::DocumentCaptureController do let(:user) { create(:user) } let!(:document_capture_session) do @@ -16,17 +16,11 @@ let(:document_capture_session_result_captured_at) { Time.zone.now + 1.second } let(:document_capture_session_result_success) { true } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_analytics session[:doc_capture_user_id] = user&.id session[:document_capture_session_uuid] = document_capture_session_uuid - - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe 'before_actions' do @@ -58,7 +52,7 @@ step: 'document_capture', selfie_check_required: false, liveness_checking_required: boolean, - }.merge(ab_test_args) + } end it 'renders the show template' do @@ -181,7 +175,7 @@ step: 'document_capture', liveness_checking_required: false, selfie_check_required: boolean, - }.merge(ab_test_args) + } end before do diff --git a/spec/controllers/idv/image_uploads_controller_spec.rb b/spec/controllers/idv/image_uploads_controller_spec.rb index 6dc3d4cb7e4..7d83a3b7989 100644 --- a/spec/controllers/idv/image_uploads_controller_spec.rb +++ b/spec/controllers/idv/image_uploads_controller_spec.rb @@ -971,7 +971,7 @@ attention_with_barcode: false, async: false, billed: true, - doc_auth_result: 'Caution', + doc_auth_result: 'Failed', user_id: user.uuid, submit_attempts: 1, remaining_submit_attempts: IdentityConfig.store.doc_auth_max_attempts - 1, @@ -990,6 +990,27 @@ selfie_live: boolean, selfie_quality_good: boolean, workflow: an_instance_of(String), + alert_failure_count: 1, + liveness_enabled: false, + vendor: 'Mock', + processed_alerts: { + failed: [{ name: '2D Barcode Content', result: 'Attention' }], + passed: [], + }, + image_metrics: { + back: { + 'GlareMetric' => 100, + 'HorizontalResolution' => 600, + 'SharpnessMetric' => 100, + 'VerticalResolution' => 600, + }, + front: { + 'GlareMetric' => 100, + 'HorizontalResolution' => 600, + 'SharpnessMetric' => 100, + 'VerticalResolution' => 600, + }, + }, ) expect_funnel_update_counts(user, 1) diff --git a/spec/controllers/idv/in_person/address_controller_spec.rb b/spec/controllers/idv/in_person/address_controller_spec.rb index 9e827a54764..2c44b317854 100644 --- a/spec/controllers/idv/in_person/address_controller_spec.rb +++ b/spec/controllers/idv/in_person/address_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::InPerson::AddressController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::InPerson::AddressController do include FlowPolicyHelper include InPersonHelper diff --git a/spec/controllers/idv/in_person/ssn_controller_spec.rb b/spec/controllers/idv/in_person/ssn_controller_spec.rb index e3f1d56fbe6..b8d9d01ded8 100644 --- a/spec/controllers/idv/in_person/ssn_controller_spec.rb +++ b/spec/controllers/idv/in_person/ssn_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::InPerson::SsnController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::InPerson::SsnController do let(:pii_from_user) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID_WITH_NO_SSN.dup } let(:flow_session) do @@ -11,15 +11,10 @@ let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_sign_in(user) subject.user_session['idv/in_person'] = flow_session stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) subject.idv_session.flow_path = 'standard' end @@ -48,7 +43,7 @@ flow_path: 'standard', step: 'ssn', same_address_as_id: true, - }.merge(ab_test_args) + } end it 'renders the show template' do @@ -118,7 +113,7 @@ success: true, errors: {}, same_address_as_id: true, - }.merge(ab_test_args) + } end it 'sends analytics_submitted event' do @@ -162,7 +157,7 @@ }, error_details: { ssn: { invalid: true } }, same_address_as_id: true, - }.merge(ab_test_args) + } end render_views diff --git a/spec/controllers/idv/in_person/state_id_controller_spec.rb b/spec/controllers/idv/in_person/state_id_controller_spec.rb index b5e14855c3c..1b195113fca 100644 --- a/spec/controllers/idv/in_person/state_id_controller_spec.rb +++ b/spec/controllers/idv/in_person/state_id_controller_spec.rb @@ -1,16 +1,12 @@ require 'rails_helper' -RSpec.describe Idv::InPerson::StateIdController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::InPerson::StateIdController do include FlowPolicyHelper include InPersonHelper let(:user) { build(:user) } let(:enrollment) { InPersonEnrollment.new } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do allow(IdentityConfig.store).to receive(:in_person_state_id_controller_enabled). and_return(true) @@ -22,7 +18,6 @@ subject.user_session['idv/in_person'] = { pii_from_user: {} } subject.idv_session.ssn = nil # This made specs pass. Might need more investigation. stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe 'before_actions' do @@ -76,7 +71,7 @@ analytics_id: 'In Person Proofing', flow_path: 'standard', step: 'state_id', - }.merge(ab_test_args) + } end it 'has non-nil presenter' do @@ -182,7 +177,7 @@ same_address_as_id: true, birth_year: dob[:year], document_zip_code: identity_doc_zipcode&.slice(0, 5), - }.merge(ab_test_args) + } end it 'logs idv_in_person_proofing_state_id_submitted' do diff --git a/spec/controllers/idv/in_person/usps_locations_controller_spec.rb b/spec/controllers/idv/in_person/usps_locations_controller_spec.rb index 707693963e0..009509b4c84 100644 --- a/spec/controllers/idv/in_person/usps_locations_controller_spec.rb +++ b/spec/controllers/idv/in_person/usps_locations_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::InPerson::UspsLocationsController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::InPerson::UspsLocationsController do let(:user) { create(:user) } let(:sp) { nil } let(:in_person_proofing_enabled) { true } diff --git a/spec/controllers/idv/in_person/verify_info_controller_spec.rb b/spec/controllers/idv/in_person/verify_info_controller_spec.rb index 93a24901d47..ebea673e80c 100644 --- a/spec/controllers/idv/in_person/verify_info_controller_spec.rb +++ b/spec/controllers/idv/in_person/verify_info_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::InPerson::VerifyInfoController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::InPerson::VerifyInfoController do let(:pii_from_user) { Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID.dup } let(:flow_session) do { pii_from_user: pii_from_user } @@ -9,16 +9,11 @@ let(:user) { create(:user, :with_phone, with: { phone: '+1 (415) 555-0130' }) } let(:service_provider) { create(:service_provider) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_sign_in(user) subject.idv_session.flow_path = 'standard' subject.idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID[:ssn] subject.user_session['idv/in_person'] = flow_session - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#step_info' do @@ -78,7 +73,7 @@ flow_path: 'standard', step: 'verify', same_address_as_id: true, - }.merge(ab_test_args), + }, ) end @@ -138,7 +133,7 @@ flow_path: 'standard', step: 'verify', same_address_as_id: true, - }.merge(ab_test_args), + }, ), ) end diff --git a/spec/controllers/idv/link_sent_controller_spec.rb b/spec/controllers/idv/link_sent_controller_spec.rb index c600e714e9c..fea657a0e01 100644 --- a/spec/controllers/idv/link_sent_controller_spec.rb +++ b/spec/controllers/idv/link_sent_controller_spec.rb @@ -1,19 +1,14 @@ require 'rails_helper' -RSpec.describe Idv::LinkSentController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::LinkSentController do let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_sign_in(user) subject.idv_session.welcome_visited = true subject.idv_session.idv_consent_given_at = Time.zone.now subject.idv_session.flow_path = 'hybrid' stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#step_info' do @@ -52,7 +47,7 @@ analytics_id: 'Doc Auth', flow_path: 'hybrid', step: 'link_sent', - }.merge(ab_test_args) + } end it 'renders the show template' do @@ -114,7 +109,7 @@ analytics_id: 'Doc Auth', flow_path: 'hybrid', step: 'link_sent', - }.merge(ab_test_args) + } end it 'invalidates future steps' do diff --git a/spec/controllers/idv/mail_only_warning_controller_spec.rb b/spec/controllers/idv/mail_only_warning_controller_spec.rb index fd41e819c50..2b8e375e7d1 100644 --- a/spec/controllers/idv/mail_only_warning_controller_spec.rb +++ b/spec/controllers/idv/mail_only_warning_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::MailOnlyWarningController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::MailOnlyWarningController do let(:user) { create(:user) } before do diff --git a/spec/controllers/idv/otp_verification_controller_spec.rb b/spec/controllers/idv/otp_verification_controller_spec.rb index 586a83d47a3..ddd3833539a 100644 --- a/spec/controllers/idv/otp_verification_controller_spec.rb +++ b/spec/controllers/idv/otp_verification_controller_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::OtpVerificationController, - allowed_extra_analytics: [:sample_bucket1, :sample_bucket2] do +RSpec.describe Idv::OtpVerificationController do let(:user) { create(:user) } let(:phone) { '2255555000' } @@ -19,13 +18,9 @@ sent_at: phone_confirmation_otp_sent_at, ) end - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end before do stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) sign_in(user) stub_verify_steps_one_and_two(user) @@ -169,7 +164,6 @@ code_matches: true, otp_delivery_preference: :sms, second_factor_attempts_count: 0, - **ab_test_args, ), ) end diff --git a/spec/controllers/idv/phone_controller_spec.rb b/spec/controllers/idv/phone_controller_spec.rb index c34bfa95667..664a4dee1c9 100644 --- a/spec/controllers/idv/phone_controller_spec.rb +++ b/spec/controllers/idv/phone_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::PhoneController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::PhoneController do include FlowPolicyHelper let(:max_attempts) { RateLimiter.max_attempts(:proof_address) } @@ -241,13 +241,7 @@ with: { phone: '+1 (415) 555-0130' } ) end - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) - end context 'when form is invalid' do let(:improbable_phone_message) { t('errors.messages.improbable_phone') } let(:improbable_otp_message) { 'is not included in the list' } @@ -309,7 +303,6 @@ phone_type: :mobile, otp_delivery_preference: '🎷', types: [], - **ab_test_args, ), ) @@ -351,7 +344,6 @@ phone_type: :mobile, otp_delivery_preference: 'sms', types: [:fixed_or_mobile], - **ab_test_args, ), ) end diff --git a/spec/controllers/idv/phone_errors_controller_spec.rb b/spec/controllers/idv/phone_errors_controller_spec.rb index 14e9003ff03..85f45b5df22 100644 --- a/spec/controllers/idv/phone_errors_controller_spec.rb +++ b/spec/controllers/idv/phone_errors_controller_spec.rb @@ -1,11 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::PhoneErrorsController, - allowed_extra_analytics: [:sample_bucket1, :sample_bucket2] do - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - +RSpec.describe Idv::PhoneErrorsController do describe '#step_info' do it 'returns a valid StepInfo object' do expect(Idv::PhoneErrorsController.step_info).to be_valid @@ -15,7 +10,6 @@ before do allow(subject).to receive(:remaining_submit_attempts).and_return(5) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) if user stub_sign_in(user) @@ -159,7 +153,6 @@ 'IdV: phone error visited', type: action, remaining_submit_attempts: 4, - **ab_test_args, ) end end @@ -212,7 +205,6 @@ 'IdV: phone error visited', type: action, remaining_submit_attempts: 4, - **ab_test_args, ) end end @@ -244,7 +236,6 @@ 'IdV: phone error visited', type: action, limiter_expires_at: attempted_at + rate_limit_window, - **ab_test_args, ) end end diff --git a/spec/controllers/idv/session_errors_controller_spec.rb b/spec/controllers/idv/session_errors_controller_spec.rb index e4e22231bfe..5b8b958185c 100644 --- a/spec/controllers/idv/session_errors_controller_spec.rb +++ b/spec/controllers/idv/session_errors_controller_spec.rb @@ -190,7 +190,7 @@ 'IdV: session error visited', hash_including( type: action.to_s, - submit_attempts_remaining: IdentityConfig.store.idv_max_attempts - 1, + remaining_submit_attempts: IdentityConfig.store.idv_max_attempts - 1, ), ) end @@ -271,7 +271,7 @@ 'IdV: session error visited', hash_including( type: action.to_s, - submit_attempts_remaining: 0, + remaining_submit_attempts: 0, ), ) end @@ -313,7 +313,7 @@ 'IdV: session error visited', hash_including( type: 'ssn_failure', - submit_attempts_remaining: 0, + remaining_submit_attempts: 0, ), ) end @@ -347,7 +347,7 @@ 'IdV: session error visited', hash_including( type: action.to_s, - submit_attempts_remaining: 0, + remaining_submit_attempts: 0, ), ) end diff --git a/spec/controllers/idv/ssn_controller_spec.rb b/spec/controllers/idv/ssn_controller_spec.rb index e4bd38ade70..d4e32ca1373 100644 --- a/spec/controllers/idv/ssn_controller_spec.rb +++ b/spec/controllers/idv/ssn_controller_spec.rb @@ -1,21 +1,16 @@ require 'rails_helper' -RSpec.describe Idv::SsnController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::SsnController do include FlowPolicyHelper let(:ssn) { Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn] } let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_sign_in(user) stub_up_to(:document_capture, idv_session: subject.idv_session) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#step_info' do @@ -54,7 +49,7 @@ analytics_id: 'Doc Auth', flow_path: 'standard', step: 'ssn', - }.merge(ab_test_args) + } end it 'renders the show template' do @@ -131,7 +126,7 @@ step: 'ssn', success: true, errors: {}, - }.merge(ab_test_args) + } end it 'updates idv_session.ssn' do @@ -189,7 +184,7 @@ ssn: [t('idv.errors.pattern_mismatch.ssn')], }, error_details: { ssn: { invalid: true } }, - }.merge(ab_test_args) + } end render_views diff --git a/spec/controllers/idv/verify_info_controller_spec.rb b/spec/controllers/idv/verify_info_controller_spec.rb index a5d376d91c5..67107ec790e 100644 --- a/spec/controllers/idv/verify_info_controller_spec.rb +++ b/spec/controllers/idv/verify_info_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Idv::VerifyInfoController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::VerifyInfoController do include FlowPolicyHelper let(:user) { create(:user) } @@ -9,18 +9,13 @@ analytics_id: 'Doc Auth', flow_path: 'standard', step: 'verify', - }.merge(ab_test_args) - end - - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } + } end before do stub_sign_in(user) stub_up_to(:ssn, idv_session: subject.idv_session) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#step_info' do @@ -61,7 +56,7 @@ analytics_id: 'Doc Auth', flow_path: 'standard', step: 'verify', - }.merge(ab_test_args), + }, ) end @@ -299,7 +294,7 @@ analytics_id: 'Doc Auth', flow_path: 'standard', step: 'verify', - }.merge(ab_test_args), + }, ), ) end diff --git a/spec/controllers/idv/welcome_controller_spec.rb b/spec/controllers/idv/welcome_controller_spec.rb index 269632a3177..793e1eebfe7 100644 --- a/spec/controllers/idv/welcome_controller_spec.rb +++ b/spec/controllers/idv/welcome_controller_spec.rb @@ -1,16 +1,11 @@ require 'rails_helper' -RSpec.describe Idv::WelcomeController, allowed_extra_analytics: [:*] do +RSpec.describe Idv::WelcomeController do let(:user) { create(:user) } - let(:ab_test_args) do - { sample_bucket1: :sample_value1, sample_bucket2: :sample_value2 } - end - before do stub_sign_in(user) stub_analytics - allow(subject).to receive(:ab_test_analytics_buckets).and_return(ab_test_args) end describe '#step_info' do @@ -41,7 +36,7 @@ { step: 'welcome', analytics_id: 'Doc Auth', - }.merge(ab_test_args) + } end it 'renders the show template' do @@ -121,7 +116,7 @@ { step: 'welcome', analytics_id: 'Doc Auth', - }.merge(ab_test_args) + } end it 'sends analytics_submitted event' do diff --git a/spec/controllers/risc/security_events_controller_spec.rb b/spec/controllers/risc/security_events_controller_spec.rb index 12f0a78108f..246e06c5fdd 100644 --- a/spec/controllers/risc/security_events_controller_spec.rb +++ b/spec/controllers/risc/security_events_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe Risc::SecurityEventsController, allowed_extra_analytics: [:*] do +RSpec.describe Risc::SecurityEventsController do include Rails.application.routes.url_helpers let(:user) { create(:user) } diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index 20886440bdf..dba27b7a034 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -993,7 +993,7 @@ def name_id_version(format_urn) authn_context_comparison: 'exact', service_provider: 'http://localhost:3000', request_signed: true, - requested_ial: 'http://idmanagement.gov/ns/assurance/loa/5', + requested_ial: 'none', endpoint: "/api/saml/auth#{path_year}", idv: false, finish_profile: false, @@ -1262,7 +1262,7 @@ def name_id_version(format_urn) authn_context: ['http://idmanagement.gov/ns/assurance/loa/5'], authn_context_comparison: 'exact', request_signed: true, - requested_ial: 'http://idmanagement.gov/ns/assurance/loa/5', + requested_ial: 'none', endpoint: "/api/saml/auth#{path_year}", idv: false, finish_profile: false, @@ -1390,6 +1390,7 @@ def name_id_version(format_urn) let(:user_identity) do @user.identities.find_by(service_provider: saml_settings.issuer) end + before do sign_in(@user) saml_get_auth(saml_settings) @@ -1757,8 +1758,10 @@ def name_id_version(format_urn) it_behaves_like 'sends the UUID', Saml::Idp::Constants::NAME_ID_FORMAT_UNSPECIFIED end + context 'when the service provider is configured with use_legacy_name_id_behavior' do let(:use_legacy_name_id_behavior) { true } + it 'sends the id, not the UUID' do generate_saml_response(user, auth_settings) @@ -1780,8 +1783,10 @@ def name_id_version(format_urn) it_behaves_like 'sends the UUID', nil end + context 'when the service provider is configured with use_legacy_name_id_behavior' do let(:use_legacy_name_id_behavior) { true } + it_behaves_like 'sends the UUID', nil end end @@ -1804,6 +1809,7 @@ def name_id_version(format_urn) context 'when the service provider is allowed to use email' do let(:email_allowed) { true } + it_behaves_like 'sends the email', Saml::Idp::Constants::NAME_ID_FORMAT_EMAIL end end diff --git a/spec/features/accessibility/idv_pages_spec.rb b/spec/features/accessibility/idv_pages_spec.rb index 85087f1fe33..d5d92c3374a 100644 --- a/spec/features/accessibility/idv_pages_spec.rb +++ b/spec/features/accessibility/idv_pages_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' require 'axe-rspec' -RSpec.feature 'Accessibility on IDV pages', :js, allowed_extra_analytics: [:*] do +RSpec.feature 'Accessibility on IDV pages', :js do describe 'IDV pages' do include IdvStepHelper diff --git a/spec/features/idv/analytics_spec.rb b/spec/features/idv/analytics_spec.rb index 0fcaa24c1e7..465b3de5145 100644 --- a/spec/features/idv/analytics_spec.rb +++ b/spec/features/idv/analytics_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' require 'csv' -RSpec.feature 'Analytics Regression', js: true, allowed_extra_analytics: [:*] do +RSpec.feature 'Analytics Regression', :js do include IdvStepHelper include InPersonHelper @@ -509,10 +509,10 @@ flow_path: 'standard', opted_in_to_in_person_proofing: false }, 'IdV: in person proofing state_id visited' => { - step: 'state_id', flow_path: 'standard', step_count: 1, analytics_id: 'In Person Proofing' + step: 'state_id', flow_path: 'standard', analytics_id: 'In Person Proofing' }, 'IdV: in person proofing state_id submitted' => { - success: true, flow_path: 'standard', step: 'state_id', step_count: 1, analytics_id: 'In Person Proofing', errors: {}, same_address_as_id: false, birth_year: '1938', document_zip_code: '12345' + success: true, flow_path: 'standard', step: 'state_id', analytics_id: 'In Person Proofing', errors: {}, same_address_as_id: false, birth_year: '1938', document_zip_code: '12345' }, 'IdV: in person proofing address visited' => { step: 'address', flow_path: 'standard', analytics_id: 'In Person Proofing', same_address_as_id: false diff --git a/spec/features/idv/clearing_and_restarting_spec.rb b/spec/features/idv/clearing_and_restarting_spec.rb index a7e5915c625..8e752b26ee4 100644 --- a/spec/features/idv/clearing_and_restarting_spec.rb +++ b/spec/features/idv/clearing_and_restarting_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe 'clearing IdV and restarting', allowed_extra_analytics: [:*] do +RSpec.describe 'clearing IdV and restarting' do include IdvStepHelper let(:user) { user_with_2fa } diff --git a/spec/features/idv/confirm_start_over_spec.rb b/spec/features/idv/confirm_start_over_spec.rb index a60e75d7db6..742086d6dd0 100644 --- a/spec/features/idv/confirm_start_over_spec.rb +++ b/spec/features/idv/confirm_start_over_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'idv gpo confirm start over', js: true, allowed_extra_analytics: [:*] do +RSpec.feature 'idv gpo confirm start over', js: true do include IdvStepHelper include DocAuthHelper diff --git a/spec/features/idv/doc_auth/address_step_spec.rb b/spec/features/idv/doc_auth/address_step_spec.rb index 0fa48a3299b..ed8969a8409 100644 --- a/spec/features/idv/doc_auth/address_step_spec.rb +++ b/spec/features/idv/doc_auth/address_step_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'doc auth verify step', :js, allowed_extra_analytics: [:*] do +RSpec.feature 'doc auth verify step', :js do include IdvStepHelper include DocAuthHelper diff --git a/spec/features/idv/doc_auth/document_capture_spec.rb b/spec/features/idv/doc_auth/document_capture_spec.rb index 2d89db5ae1c..e04ef1320d2 100644 --- a/spec/features/idv/doc_auth/document_capture_spec.rb +++ b/spec/features/idv/doc_auth/document_capture_spec.rb @@ -1,28 +1,25 @@ require 'rails_helper' -RSpec.feature 'document capture step', :js, allowed_extra_analytics: [:*] do +RSpec.feature 'document capture step', :js do include IdvStepHelper include DocAuthHelper include DocCaptureHelper include ActionView::Helpers::DateHelper let(:max_attempts) { IdentityConfig.store.doc_auth_max_attempts } + let(:fake_analytics) { FakeAnalytics.new } + before(:each) do - allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(@fake_analytics) + allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) allow_any_instance_of(ServiceProviderSession).to receive(:sp_name).and_return(@sp_name) end before(:all) do - @user = user_with_2fa - @fake_analytics = FakeAnalytics.new @sp_name = 'Test SP' + @user = user_with_2fa end - after(:all) do - @user.destroy - @fake_analytics = '' - @sp_name = '' - end + after(:all) { @user.destroy } context 'standard desktop flow' do before do @@ -31,60 +28,6 @@ complete_doc_auth_steps_before_document_capture_step end - context 'wrong doc type is uploaded', allow_browser_log: true do - it 'try again and page show doc type inline error message' do - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_wrong_doc_type.yml' - ), - ) - submit_images - message = strip_tags(t('doc_auth.errors.doc_type_not_supported_heading')) - expect(page).to have_content(message) - detail_message = strip_tags(t('doc_auth.errors.doc.doc_type_check')) - warning_message = strip_tags( - t( - 'idv.failure.attempts_html', - count: IdentityConfig.store.doc_auth_max_attempts - 1, - ), - ) - expect(page).to have_content(detail_message) - expect(page).to have_content(warning_message) - expect(page).to have_current_path(idv_document_capture_path) - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - inline_error = strip_tags(t('doc_auth.errors.card_type')) - expect(page).to have_content(inline_error) - end - end - - context 'attention barcode with invalid pii is uploaded', allow_browser_log: true do - let(:desktop_selfie_mode) { false } - # test disabled desktop selfie mode allows upload for doc auth w/o selfie - before do - allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode). - and_return(desktop_selfie_mode) - end - it 'try again and page show doc type inline error message' do - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_barcode_attention_no_address.yml' - ), - ) - submit_images - - expect(page).to have_content(t('doc_auth.errors.alerts.address_check')) - expect(page).to have_current_path(idv_document_capture_path) - - click_try_again - attach_images - submit_images - expect(page).to have_current_path(idv_ssn_path) - end - end - context 'rate limits calls to backend docauth vendor', allow_browser_log: true do before do allow(IdentityConfig.store).to receive(:doc_auth_max_attempts).and_return(max_attempts) @@ -116,16 +59,14 @@ it 'logs the rate limited analytics event for doc_auth' do attach_and_submit_images - expect(@fake_analytics).to have_logged_event( + expect(fake_analytics).to have_logged_event( 'Rate Limit Reached', limiter_type: :idv_doc_auth, ) end context 'successfully processes image on last attempt' do - before do - DocAuth::Mock::DocAuthMockClient.reset! - end + before { DocAuth::Mock::DocAuthMockClient.reset! } it 'proceeds to the next page with valid info' do expect(page).to have_current_path(idv_document_capture_url) @@ -175,19 +116,13 @@ expect(page).not_to have_content(t('doc_auth.headings.document_capture_selfie')) # doc auth is successful while liveness is not req'd - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_no_liveness.yml' - ), - ) + use_id_image('ial2_test_credential_no_liveness.yml') submit_images expect(page).to have_current_path(idv_ssn_url) expect_costing_for_document expect(DocAuthLog.find_by(user_id: @user.id).state).to eq('NY') - expect(page).to have_current_path(idv_ssn_url) fill_out_ssn_form_ok click_idv_continue complete_verify_step @@ -198,6 +133,7 @@ context 'selfie check' do let(:selfie_check_enabled) { true } + before do allow(IdentityConfig.store).to receive(:use_vot_in_sp_requests).and_return(true) end @@ -272,199 +208,135 @@ end end - context 'selfie with error is uploaded' do - context 'IPP enabled' do - let(:ipp_service_provider) do - create(:service_provider, :active, :in_person_proofing_enabled) - end - - before do - allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) - allow(IdentityConfig.store).to receive( - :in_person_proofing_opt_in_enabled, - ).and_return(true) - allow_any_instance_of(ServiceProvider).to receive( - :in_person_proofing_enabled, - ).and_return(true) - allow(IdentityConfig.store).to receive(:doc_auth_max_attempts).and_return(99) - perform_in_browser(:mobile) do - visit_idp_from_sp_with_ial2( - :oidc, - **{ client_id: ipp_service_provider.issuer, - biometric_comparison_required: true }, - ) - sign_in_and_2fa_user(@user) - complete_up_to_how_to_verify_step_for_opt_in_ipp( - biometric_comparison_required: true, - ) - complete_verify_step - end - end - + context 'documents or selfie with error is uploaded' do + shared_examples 'it has correct error displays' do + # when there are multiple doc auth errors on front and back it 'shows the correct error message for the given error' do - # when there are multiple doc auth errors on front and back - perform_in_browser(:mobile) do - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_multiple_doc_auth_failures_both_sides.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_multiple_doc_auth_failures_both_sides.yml' - ), - ) - + use_id_image('ial2_test_credential_multiple_doc_auth_failures_both_sides.yml') + use_selfie_image('ial2_test_credential_multiple_doc_auth_failures_both_sides.yml') submit_images - review_issues_h1_heading = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_issues_h1_heading) - - review_issues_subheading = strip_tags(t('doc_auth.errors.rate_limited_subheading')) - expect(page).not_to have_selector('h2', text: review_issues_subheading) + expect_rate_limited_header(true) - review_issues_body_message = strip_tags(t('doc_auth.errors.general.no_liveness')) - expect(page).to have_content(review_issues_body_message) + expect_try_taking_new_pictures + expect_review_issues_body_message('doc_auth.errors.general.no_liveness') + expect_rate_limit_warning(max_attempts - 1) - review_issues_rate_limit_warning = strip_tags( - t( - 'idv.failure.attempts_html', - count: max_attempts - 1, - ), - ) - expect(page).to have_content(review_issues_rate_limit_warning) + expect_to_try_again + expect_resubmit_page_h1_copy - click_try_again - expect(page).to have_current_path(idv_document_capture_path) + expect_resubmit_page_body_copy('doc_auth.errors.general.no_liveness') + expect_resubmit_page_inline_error_messages(2) + expect_resubmit_page_inline_selfie_error_message(false) - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) + # Wrong doc type is uploaded + use_id_image('ial2_test_credential_wrong_doc_type.yml') + use_selfie_image('ial2_test_portrait_match_success.yml') + submit_images - resubmit_page_body_copy = strip_tags(t('doc_auth.errors.general.no_liveness')) - expect(page).to have_content(resubmit_page_body_copy) + expect_rate_limited_header(false) + expect_try_taking_new_pictures(false) + expect_review_issues_body_message('doc_auth.errors.doc_type_not_supported_heading') + expect_review_issues_body_message('doc_auth.errors.doc.doc_type_check') + expect_rate_limit_warning(max_attempts - 2) - resubmit_page_inline_error_messages = strip_tags( - t('doc_auth.errors.general.fallback_field_level'), - ) - expect(page).to have_content(resubmit_page_inline_error_messages).twice + expect_to_try_again + expect_resubmit_page_h1_copy - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) + expect_review_issues_body_message('doc_auth.errors.card_type') + expect_resubmit_page_inline_selfie_error_message(false) # when there are multiple front doc auth errors - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_multiple_doc_auth_failures_front_side_only.yml' - ), + use_id_image('ial2_test_credential_multiple_doc_auth_failures_front_side_only.yml') + use_selfie_image( + 'ial2_test_credential_multiple_doc_auth_failures_front_side_only.yml', ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_multiple_doc_auth_failures_front_side_only.yml' - ), - ) - submit_images - review_issues_h1_heading = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_issues_h1_heading) - - review_issues_subheading = strip_tags(t('doc_auth.errors.rate_limited_subheading')) - expect(page).not_to have_selector('h2', text: review_issues_subheading) - - review_issues_body_message = strip_tags( - t('doc_auth.errors.general.multiple_front_id_failures'), - ) - expect(page).to have_content(review_issues_body_message) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 2), + expect_rate_limited_header(true) + expect_try_taking_new_pictures(false) + expect_review_issues_body_message( + 'doc_auth.errors.general.multiple_front_id_failures', ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) + expect_rate_limit_warning(max_attempts - 3) - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) + expect_to_try_again + expect_resubmit_page_h1_copy - resubmit_page_body_copy = strip_tags( - t('doc_auth.errors.general.multiple_front_id_failures'), - ) - expect(page).to have_content(resubmit_page_body_copy) + expect_resubmit_page_body_copy('doc_auth.errors.general.multiple_front_id_failures') + expect_resubmit_page_inline_error_messages(1) + expect_resubmit_page_inline_selfie_error_message(false) - resubmit_page_inline_error_messages = strip_tags( - t('doc_auth.errors.general.fallback_field_level'), + # when there are multiple back doc auth errors + use_id_image('ial2_test_credential_multiple_doc_auth_failures_back_side_only.yml') + use_selfie_image( + 'ial2_test_credential_multiple_doc_auth_failures_back_side_only.yml', ) - expect(page).to have_content(resubmit_page_inline_error_messages).once + submit_images - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), + expect_rate_limited_header(true) + expect_try_taking_new_pictures(false) + expect_review_issues_body_message( + 'doc_auth.errors.general.multiple_back_id_failures', ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) + expect_rate_limit_warning(max_attempts - 4) - # when there are multiple back doc auth errors + expect_to_try_again + expect_resubmit_page_h1_copy - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_multiple_doc_auth_failures_back_side_only.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_multiple_doc_auth_failures_back_side_only.yml' - ), - ) + expect_resubmit_page_body_copy('doc_auth.errors.general.multiple_back_id_failures') + expect_resubmit_page_inline_error_messages(1) + expect_resubmit_page_inline_selfie_error_message(false) + # attention barcode with invalid pii is uploaded + use_id_image('ial2_test_credential_barcode_attention_no_address.yml') + use_selfie_image('ial2_test_portrait_match_success.yml') submit_images - review_issues_h1_heading = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_issues_h1_heading) - - review_issues_subheading = strip_tags(t('doc_auth.errors.rate_limited_subheading')) - expect(page).not_to have_selector('h2', text: review_issues_subheading) - - review_issues_body_message = strip_tags( - t('doc_auth.errors.general.multiple_back_id_failures'), - ) - expect(page).to have_content(review_issues_body_message) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 3), - ) - expect(page).to have_content(review_issues_rate_limit_warning) + expect(page).to have_content(t('doc_auth.errors.alerts.address_check')) + expect(page).to have_current_path(idv_document_capture_path) click_try_again - expect(page).to have_current_path(idv_document_capture_path) - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) + # And finally, after lots of errors, we can still succeed + attach_images + submit_images - resubmit_page_body_copy = strip_tags( - t('doc_auth.errors.general.multiple_back_id_failures'), - ) - expect(page).to have_content(resubmit_page_body_copy) + expect(page).to have_current_path(idv_ssn_path) + end + end + end - resubmit_page_inline_error_messages = strip_tags( - t('doc_auth.errors.general.fallback_field_level'), - ) - expect(page).to have_content(resubmit_page_inline_error_messages).once + context 'IPP enabled' do + let(:ipp_service_provider) do + create(:service_provider, :active, :in_person_proofing_enabled) + end - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), + before do + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) + allow(IdentityConfig.store).to receive( + :in_person_proofing_opt_in_enabled, + ).and_return(true) + allow_any_instance_of(ServiceProvider).to receive( + :in_person_proofing_enabled, + ).and_return(true) + allow(IdentityConfig.store).to receive(:doc_auth_max_attempts).and_return(99) + perform_in_browser(:mobile) do + visit_idp_from_sp_with_ial2( + :oidc, + **{ client_id: ipp_service_provider.issuer, + biometric_comparison_required: true }, + ) + sign_in_and_2fa_user(@user) + complete_up_to_how_to_verify_step_for_opt_in_ipp( + biometric_comparison_required: true, ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) + complete_verify_step end end + + it_should_behave_like 'it has correct error displays' end context 'IPP not enabled' do @@ -477,679 +349,13 @@ end end - it 'shows the correct error message for the given error' do - # when the only error is a doc auth error - - perform_in_browser(:mobile) do - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_fail_selfie_pass.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_fail_selfie_pass.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags(t('doc_auth.errors.dpi.top_msg')) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t( - 'idv.failure.attempts_html', - count: max_attempts - 1, - ), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - inline_error_message = strip_tags(t('doc_auth.errors.dpi.failed_short')) - expect(page).to have_content(inline_error_message) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags(t('doc_auth.errors.dpi.top_msg')) - expect(page).to have_content(resubmit_page_body_copy) - - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) - - expect(page).to have_current_path(idv_document_capture_url) - - # when doc auth result passes but liveness fails - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_no_liveness.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_no_liveness.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags( - t('doc_auth.errors.selfie_not_live_or_poor_quality_heading'), - ) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags( - t('doc_auth.errors.alerts.selfie_not_live_or_poor_quality'), - ) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 2), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags( - t('doc_auth.errors.alerts.selfie_not_live_or_poor_quality'), - ) - expect(page).to have_content(resubmit_page_body_copy) - - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).to have_content(resubmit_page_inline_selfie_error_message) - - # when there are both doc auth errors and liveness errors - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_fail_and_no_liveness.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_fail_and_no_liveness.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags(t('doc_auth.errors.dpi.top_msg')) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 3), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags(t('doc_auth.errors.dpi.top_msg')) - expect(page).to have_content(resubmit_page_body_copy) - - inline_error_message = strip_tags(t('doc_auth.errors.dpi.failed_short')) - expect(page).to have_content(inline_error_message) - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) - - # when there are both doc auth errors and face match errors - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_fail_face_match_fail.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_fail_face_match_fail.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags(t('doc_auth.errors.dpi.top_msg')) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 4), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags(t('doc_auth.errors.dpi.top_msg')) - expect(page).to have_content(resubmit_page_body_copy) - - inline_error_message = strip_tags(t('doc_auth.errors.dpi.failed_short')) - expect(page).to have_content(inline_error_message) - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) - - # when doc auth result and liveness pass but face match fails - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_portrait_match_failure.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_portrait_match_failure.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.selfie_fail_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags(t('doc_auth.errors.general.selfie_failure')) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 5), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags(t('doc_auth.errors.general.selfie_failure')) - expect(page).to have_content(resubmit_page_body_copy) - - inline_error_message = strip_tags( - t('doc_auth.errors.general.multiple_front_id_failures'), - ) - expect(page).to have_content(inline_error_message) - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).to have_content(resubmit_page_inline_selfie_error_message) - - # when there is a doc auth error on one side of the ID and face match errors - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_back_fail_doc_auth_face_match_errors.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_back_fail_doc_auth_face_match_errors.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_nbsp( - t('doc_auth.errors.alerts.barcode_content_check'), - ) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 6), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_nbsp( - t('doc_auth.errors.alerts.barcode_content_check'), - ) - expect(page).to have_content(resubmit_page_body_copy) - - inline_error_message = strip_tags(t('doc_auth.errors.general.fallback_field_level')) - expect(page).to have_content(inline_error_message) - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) - - # when there is a doc auth error on one side of the ID and a liveness error - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_back_fail_doc_auth_liveness_errors.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_back_fail_doc_auth_liveness_errors.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_nbsp( - t('doc_auth.errors.alerts.barcode_content_check'), - ) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 7), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_nbsp( - t('doc_auth.errors.alerts.barcode_content_check'), - ) - expect(page).to have_content(resubmit_page_body_copy) - - inline_error_message = strip_tags(t('doc_auth.errors.general.fallback_field_level')) - expect(page).to have_content(inline_error_message) - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) - - # when doc auth result is "attention" and face match errors - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_attention_face_match_fail.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_attention_face_match_fail.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags(t('doc_auth.errors.general.no_liveness')) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 8), - ) - expect(page).to have_content(review_issues_rate_limit_warning).once - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags(t('doc_auth.errors.general.no_liveness')) - expect(page).to have_content(resubmit_page_body_copy) - - inline_error_message = strip_tags(t('doc_auth.errors.general.fallback_field_level')) - expect(page).to have_content(inline_error_message) - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) - - # when doc auth passes but there are both liveness errors and face match errors - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_liveness_fail_face_match_fail.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_liveness_fail_face_match_fail.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags( - t('doc_auth.errors.selfie_not_live_or_poor_quality_heading'), - ) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags( - t('doc_auth.errors.alerts.selfie_not_live_or_poor_quality'), - ) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 9), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags( - t('doc_auth.errors.alerts.selfie_not_live_or_poor_quality'), - ) - expect(page).to have_content(resubmit_page_body_copy) - - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).to have_content(resubmit_page_inline_selfie_error_message) - - # when doc auth, liveness, and face match pass but PII validation fails - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_selfie_pass_pii_fail.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_doc_auth_selfie_pass_pii_fail.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.rate_limited_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags(t('doc_auth.errors.alerts.address_check')) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 10), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags(t('doc_auth.errors.alerts.address_check')) - expect(page).to have_content(resubmit_page_body_copy) - - inline_error_message = strip_tags( - t('doc_auth.errors.general.multiple_front_id_failures'), - ) - expect(page).to have_content(inline_error_message) - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) - - # when there are both face match errors and pii errors - - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_face_match_fail_and_pii_fail.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_face_match_fail_and_pii_fail.yml' - ), - ) - - submit_images - - review_page_h1_copy = strip_tags(t('doc_auth.errors.selfie_fail_heading')) - expect(page).to have_content(review_page_h1_copy) - - review_page_body_copy = strip_tags(t('doc_auth.errors.general.selfie_failure')) - expect(page).to have_content(review_page_body_copy) - - review_issues_rate_limit_warning = strip_tags( - t('idv.failure.attempts_html', count: max_attempts - 11), - ) - expect(page).to have_content(review_issues_rate_limit_warning) - - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - - resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) - expect(page).to have_content(resubmit_page_h1_copy) - - resubmit_page_body_copy = strip_tags(t('doc_auth.errors.general.selfie_failure')) - expect(page).to have_content(resubmit_page_body_copy) - - inline_error_message = strip_tags( - t('doc_auth.errors.general.multiple_front_id_failures'), - ) - expect(page).to have_content(inline_error_message) - resubmit_page_inline_selfie_error_message = strip_tags( - t('doc_auth.errors.general.selfie_failure'), - ) - expect(page).to have_content(resubmit_page_inline_selfie_error_message) - end - end - end - - it 'try again and page show no liveness inline error message' do - visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true) - sign_in_and_2fa_user(@user) - complete_doc_auth_steps_before_document_capture_step - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_no_liveness.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_no_liveness.yml' - ), - ) - submit_images - message = strip_tags(t('doc_auth.errors.selfie_not_live_or_poor_quality_heading')) - expect(page).to have_content(message) - detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_not_live_or_poor_quality')) - warning_message = strip_tags( - t( - 'idv.failure.attempts_html', - count: IdentityConfig.store.doc_auth_max_attempts - 1, - ), - ) - expect(page).to have_content("#{detail_message}\n#{warning_message}") - review_issues_header = strip_tags( - t('doc_auth.errors.selfie_not_live_or_poor_quality_heading'), - ) - expect(page).to have_content(review_issues_header) - expect(page).to have_current_path(idv_document_capture_path) - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure')) - expect(page).to have_content(inline_error) - end - - it 'try again and page show poor quality inline error message' do - visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true) - sign_in_and_2fa_user(@user) - complete_doc_auth_steps_before_document_capture_step - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_poor_quality.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_poor_quality.yml' - ), - ) - submit_images - message = strip_tags(t('doc_auth.errors.selfie_not_live_or_poor_quality_heading')) - expect(page).to have_content(message) - detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_not_live_or_poor_quality')) - warning_message = strip_tags( - t( - 'idv.failure.attempts_html', - count: IdentityConfig.store.doc_auth_max_attempts - 1, - ), - ) - expect(page).to have_content("#{detail_message}\n#{warning_message}") - review_issues_header = strip_tags( - t('doc_auth.errors.selfie_not_live_or_poor_quality_heading'), - ) - expect(page).to have_content(review_issues_header) - expect(page).to have_current_path(idv_document_capture_path) - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure')) - expect(page).to have_content(inline_error) - end - - it 'try again and page show selfie fail inline error message' do - visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true) - sign_in_and_2fa_user(@user) - complete_doc_auth_steps_before_document_capture_step - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_portrait_match_failure.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_portrait_match_failure.yml' - ), - ) - submit_images - message = strip_tags(t('doc_auth.errors.selfie_fail_heading')) - expect(page).to have_content(message) - detail_message = strip_tags(t('doc_auth.errors.general.selfie_failure')) - warning_message = strip_tags( - t( - 'idv.failure.attempts_html', - count: IdentityConfig.store.doc_auth_max_attempts - 1, - ), - ) - expect(page).to have_content("#{detail_message}\n#{warning_message}") - review_issues_header = strip_tags( - t('doc_auth.errors.selfie_fail_heading'), - ) - expect(page).to have_content(review_issues_header) - expect(page).to have_current_path(idv_document_capture_path) - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure')) - expect(page).to have_content(inline_error) - end - end - context 'with Attention with Barcode' do - it 'try again and page show selfie fail inline error message' do - visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true) - sign_in_and_2fa_user(@user) - complete_doc_auth_steps_before_document_capture_step - attach_images( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_barcode_attention_liveness_fail.yml' - ), - ) - attach_selfie( - Rails.root.join( - 'spec', 'fixtures', - 'ial2_test_credential_barcode_attention_liveness_fail.yml' - ), - ) - submit_images - message = strip_tags(t('doc_auth.errors.selfie_not_live_or_poor_quality_heading')) - expect(page).to have_content(message) - detail_message = strip_tags(t('doc_auth.errors.alerts.selfie_not_live_or_poor_quality')) - warning_message = strip_tags( - t( - 'idv.failure.attempts_html', - count: IdentityConfig.store.doc_auth_max_attempts - 1, - ), - ) - expect(page).to have_content("#{detail_message}\n#{warning_message}") - review_issues_header = strip_tags( - t('doc_auth.errors.selfie_not_live_or_poor_quality_heading'), - ) - expect(page).to have_content(review_issues_header) - expect(page).to have_current_path(idv_document_capture_path) - click_try_again - expect(page).to have_current_path(idv_document_capture_path) - inline_error = strip_tags(t('doc_auth.errors.general.selfie_failure')) - expect(page).to have_content(inline_error) + it_should_behave_like 'it has correct error displays' end end context 'when selfie check is not enabled (flag off, and/or in production)' do let(:selfie_check_enabled) { false } + it 'proceeds to the next page with valid info, excluding a selfie image' do perform_in_browser(:mobile) do visit_idp_from_oidc_sp_with_ial2 @@ -1181,12 +387,15 @@ end end end + context 'on desktop' do let(:desktop_selfie_mode) { false } + before do allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode). and_return(desktop_selfie_mode) end + describe 'when desktop selfie not allowed' do it 'can only proceed to link sent page' do perform_in_browser(:desktop) do @@ -1203,8 +412,10 @@ end end end + describe 'when desktop selfie is allowed' do let(:desktop_selfie_mode) { true } + it 'proceed to the next page with valid info, including a selfie image' do perform_in_browser(:desktop) do visit_idp_from_oidc_sp_with_ial2(biometric_comparison_required: true) @@ -1239,12 +450,14 @@ context 'when ipp is enabled' do let(:in_person_doc_auth_button_enabled) { true } let(:sp_ipp_enabled) { true } + before do allow(IdentityConfig.store).to receive(:in_person_doc_auth_button_enabled). and_return(in_person_doc_auth_button_enabled) allow(Idv::InPersonConfig).to receive(:enabled_for_issuer?).with(anything). and_return(sp_ipp_enabled) end + describe 'when ipp is selected' do it 'proceed to the next page and start ipp' do perform_in_browser(:desktop) do @@ -1271,6 +484,82 @@ end end + def expect_rate_limited_header(expected_to_be_present) + review_issues_h1_heading = strip_tags(t('doc_auth.errors.rate_limited_heading')) + if expected_to_be_present + expect(page).to have_content(review_issues_h1_heading) + else + expect(page).not_to have_content(review_issues_h1_heading) + end + end + + def expect_try_taking_new_pictures(expected_to_be_present = true) + expected_message = strip_tags( + t('doc_auth.errors.rate_limited_subheading'), + ) + if expected_to_be_present + expect(page).to have_content expected_message + else + expect(page).not_to have_content expected_message + end + end + + def expect_review_issues_body_message(translation_key) + review_issues_body_message = strip_tags(t(translation_key)) + expect(page).to have_content(review_issues_body_message) + end + + def expect_rate_limit_warning(expected_remaining_attempts) + review_issues_rate_limit_warning = strip_tags( + t( + 'idv.failure.attempts_html', + count: expected_remaining_attempts, + ), + ) + expect(page).to have_content(review_issues_rate_limit_warning) + end + + def expect_resubmit_page_h1_copy + resubmit_page_h1_copy = strip_tags(t('doc_auth.headings.review_issues')) + expect(page).to have_content(resubmit_page_h1_copy) + end + + def expect_resubmit_page_body_copy(translation_key) + resubmit_page_body_copy = strip_tags(t(translation_key)) + expect(page).to have_content(resubmit_page_body_copy) + end + + def expect_resubmit_page_inline_error_messages(expected_count) + resubmit_page_inline_error_messages = strip_tags( + t('doc_auth.errors.general.fallback_field_level'), + ) + expect(page).to have_content(resubmit_page_inline_error_messages).exactly(expected_count) + end + + def expect_resubmit_page_inline_selfie_error_message(should_be_present) + resubmit_page_inline_selfie_error_message = strip_tags( + t('doc_auth.errors.general.selfie_failure'), + ) + if should_be_present + expect(page).to have_content(resubmit_page_inline_selfie_error_message) + else + expect(page).not_to have_content(resubmit_page_inline_selfie_error_message) + end + end + + def expect_to_try_again + click_try_again + expect(page).to have_current_path(idv_document_capture_path) + end + + def use_id_image(filename) + attach_images Rails.root.join('spec', 'fixtures', filename) + end + + def use_selfie_image(filename) + attach_selfie Rails.root.join('spec', 'fixtures', filename) + end + def expect_costing_for_document %i[acuant_front_image acuant_back_image acuant_result].each do |cost_type| expect(costing_for(cost_type)).to be_present @@ -1282,11 +571,11 @@ def costing_for(cost_type) end end -RSpec.feature 'direct access to IPP on desktop', :js, allowed_extra_analytics: [:*] do +RSpec.feature 'direct access to IPP on desktop', :js do include IdvStepHelper include DocAuthHelper - context 'direct access to IPP before handoff page' do - let(:in_person_proofing_enabled) { true } + + context 'before handoff page' do let(:sp_ipp_enabled) { true } let(:in_person_proofing_opt_in_enabled) { true } let(:biometric_comparison_required) { true } @@ -1294,19 +583,13 @@ def costing_for(cost_type) before do service_provider = create(:service_provider, :active, :in_person_proofing_enabled) - unless sp_ipp_enabled - service_provider.in_person_proofing_enabled = false - service_provider.save! - end allow(IdentityConfig.store).to receive(:doc_auth_selfie_desktop_test_mode).and_return(false) - allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return( - in_person_proofing_enabled, - ) + allow(IdentityConfig.store).to receive(:in_person_proofing_enabled).and_return(true) allow(IdentityConfig.store).to receive(:in_person_proofing_opt_in_enabled).and_return( in_person_proofing_opt_in_enabled, ) allow_any_instance_of(ServiceProvider).to receive(:in_person_proofing_enabled). - and_return(sp_ipp_enabled) + and_return(false) visit_idp_from_sp_with_ial2( :oidc, **{ client_id: service_provider.issuer, @@ -1314,20 +597,22 @@ def costing_for(cost_type) ) sign_in_via_branded_page(user) complete_doc_auth_steps_before_agreement_step + + visit idv_document_capture_path(step: 'hybrid_handoff') end - shared_examples 'does not allow direct ipp access' do + context 'when selfie is enabled' do it 'redirects back to agreement page' do - visit idv_document_capture_path(step: 'hybrid_handoff') expect(page).to have_current_path(idv_agreement_path) end end - context 'when selfie is enabled' do - it_behaves_like 'does not allow direct ipp access' - end + context 'when selfie is disabled' do let(:biometric_comparison_required) { false } - it_behaves_like 'does not allow direct ipp access' + + it 'redirects back to agreement page' do + expect(page).to have_current_path(idv_agreement_path) + end end end end diff --git a/spec/features/idv/doc_auth/how_to_verify_spec.rb b/spec/features/idv/doc_auth/how_to_verify_spec.rb index 10600f5e5b8..5de62644584 100644 --- a/spec/features/idv/doc_auth/how_to_verify_spec.rb +++ b/spec/features/idv/doc_auth/how_to_verify_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'how to verify step', js: true, allowed_extra_analytics: [:*] do +RSpec.feature 'how to verify step', js: true do include IdvHelper include DocAuthHelper diff --git a/spec/features/idv/doc_auth/link_sent_spec.rb b/spec/features/idv/doc_auth/link_sent_spec.rb index a8ffd86fa08..82a5b0e76cc 100644 --- a/spec/features/idv/doc_auth/link_sent_spec.rb +++ b/spec/features/idv/doc_auth/link_sent_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'doc auth link sent step', allowed_extra_analytics: [:*] do +RSpec.feature 'doc auth link sent step' do include IdvStepHelper include DocAuthHelper include DocCaptureHelper diff --git a/spec/features/idv/hybrid_mobile/entry_spec.rb b/spec/features/idv/hybrid_mobile/entry_spec.rb index 1bafd59bf40..9b252f78109 100644 --- a/spec/features/idv/hybrid_mobile/entry_spec.rb +++ b/spec/features/idv/hybrid_mobile/entry_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'mobile hybrid flow entry', js: true, allowed_extra_analytics: [:*] do +RSpec.feature 'mobile hybrid flow entry', :js do include IdvStepHelper let(:link_sent_via_sms) do diff --git a/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb b/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb index 99637e6dbd2..e1b04ea52ed 100644 --- a/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb +++ b/spec/features/idv/hybrid_mobile/hybrid_mobile_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe 'Hybrid Flow', :allow_net_connect_on_start, allowed_extra_analytics: [:*] do +RSpec.describe 'Hybrid Flow', :allow_net_connect_on_start do include IdvHelper include IdvStepHelper include DocAuthHelper diff --git a/spec/features/idv/in_person_threatmetrix_spec.rb b/spec/features/idv/in_person_threatmetrix_spec.rb index 7b4745487cc..01bd3a6226a 100644 --- a/spec/features/idv/in_person_threatmetrix_spec.rb +++ b/spec/features/idv/in_person_threatmetrix_spec.rb @@ -2,7 +2,7 @@ require 'action_account' require 'axe-rspec' -RSpec.describe 'In Person Proofing Threatmetrix', js: true, allowed_extra_analytics: [:*] do +RSpec.describe 'In Person Proofing Threatmetrix', js: true do include InPersonHelper let(:sp) { :oidc } diff --git a/spec/features/idv/outage_spec.rb b/spec/features/idv/outage_spec.rb index 16da42c3fec..e7025c608f2 100644 --- a/spec/features/idv/outage_spec.rb +++ b/spec/features/idv/outage_spec.rb @@ -12,7 +12,7 @@ def sign_in_with_idv_required(user:, sms_or_totp: :sms) click_submit_default end -RSpec.feature 'IdV Outage Spec', allowed_extra_analytics: [:*] do +RSpec.feature 'IdV Outage Spec' do include PersonalKeyHelper include IdvStepHelper diff --git a/spec/features/idv/phone_errors_spec.rb b/spec/features/idv/phone_errors_spec.rb index 4b33e565e77..6313e740354 100644 --- a/spec/features/idv/phone_errors_spec.rb +++ b/spec/features/idv/phone_errors_spec.rb @@ -1,5 +1,6 @@ require 'rails_helper' -RSpec.feature 'phone errors', :js, allowed_extra_analytics: [:*] do + +RSpec.feature 'phone errors', :js do include IdvStepHelper include IdvHelper diff --git a/spec/features/idv/proof_address_rate_limit_spec.rb b/spec/features/idv/proof_address_rate_limit_spec.rb index 52ed6b61ed6..f1605506b0e 100644 --- a/spec/features/idv/proof_address_rate_limit_spec.rb +++ b/spec/features/idv/proof_address_rate_limit_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'address proofing rate limit', allowed_extra_analytics: [:*] do +RSpec.feature 'address proofing rate limit' do include IdvStepHelper include IdvHelper diff --git a/spec/features/idv/puerto_rican_address_spec.rb b/spec/features/idv/puerto_rican_address_spec.rb index fa57bae9717..8bb24149a5b 100644 --- a/spec/features/idv/puerto_rican_address_spec.rb +++ b/spec/features/idv/puerto_rican_address_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe 'proofing flow with a Puerto Rican document', :js, allowed_extra_analytics: [:*] do +RSpec.describe 'proofing flow with a Puerto Rican document', :js do include DocAuthHelper include IdvStepHelper diff --git a/spec/features/idv/sp_handoff_spec.rb b/spec/features/idv/sp_handoff_spec.rb index a9a6c8ece96..e72e00cdf73 100644 --- a/spec/features/idv/sp_handoff_spec.rb +++ b/spec/features/idv/sp_handoff_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'IdV SP handoff', :email, allowed_extra_analytics: [:*] do +RSpec.feature 'IdV SP handoff', :email do include SamlAuthHelper include IdvStepHelper diff --git a/spec/features/idv/steps/forgot_password_step_spec.rb b/spec/features/idv/steps/forgot_password_step_spec.rb index 67f66c84f9a..91d8f18986e 100644 --- a/spec/features/idv/steps/forgot_password_step_spec.rb +++ b/spec/features/idv/steps/forgot_password_step_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'forgot password step', :js, allowed_extra_analytics: [:*] do +RSpec.feature 'forgot password step', :js do include IdvStepHelper it 'goes to the forgot password page from the enter password page' do diff --git a/spec/features/idv/steps/in_person/address_spec.rb b/spec/features/idv/steps/in_person/address_spec.rb index def68be5983..a3bb3a03ae3 100644 --- a/spec/features/idv/steps/in_person/address_spec.rb +++ b/spec/features/idv/steps/in_person/address_spec.rb @@ -1,8 +1,6 @@ require 'rails_helper' -RSpec.describe 'doc auth In person proofing residential address step', - js: true, - allowed_extra_analytics: [:*] do +RSpec.describe 'doc auth In person proofing residential address step', :js do include IdvStepHelper include InPersonHelper diff --git a/spec/features/idv/steps/in_person/ssn_spec.rb b/spec/features/idv/steps/in_person/ssn_spec.rb index 364ba60dd80..88156e68063 100644 --- a/spec/features/idv/steps/in_person/ssn_spec.rb +++ b/spec/features/idv/steps/in_person/ssn_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe 'doc auth IPP ssn step', js: true, allowed_extra_analytics: [:*] do +RSpec.describe 'doc auth IPP ssn step', :js do include IdvStepHelper include InPersonHelper diff --git a/spec/features/idv/steps/in_person/state_id_50_50_spec.rb b/spec/features/idv/steps/in_person/state_id_50_50_spec.rb index 19cfff04a87..d08dd7c2a71 100644 --- a/spec/features/idv/steps/in_person/state_id_50_50_spec.rb +++ b/spec/features/idv/steps/in_person/state_id_50_50_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' -RSpec.describe 'state id 50/50 state', js: true, allowed_extra_analytics: [:*], - allow_browser_log: true do +RSpec.describe 'state id 50/50 state', :js, allow_browser_log: true do include IdvStepHelper include InPersonHelper diff --git a/spec/features/idv/steps/in_person/state_id_controller_spec.rb b/spec/features/idv/steps/in_person/state_id_controller_spec.rb index d61427536d6..52275c4d36a 100644 --- a/spec/features/idv/steps/in_person/state_id_controller_spec.rb +++ b/spec/features/idv/steps/in_person/state_id_controller_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe 'state id controller enabled', js: true, allowed_extra_analytics: [:*] do +RSpec.describe 'state id controller enabled', :js do include IdvStepHelper include InPersonHelper diff --git a/spec/features/idv/steps/in_person/state_id_step_spec.rb b/spec/features/idv/steps/in_person/state_id_step_spec.rb index 64b2264bfbf..1d3e5b96353 100644 --- a/spec/features/idv/steps/in_person/state_id_step_spec.rb +++ b/spec/features/idv/steps/in_person/state_id_step_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe 'doc auth IPP state ID step', js: true, allowed_extra_analytics: [:*] do +RSpec.describe 'doc auth IPP state ID step', js: true do include IdvStepHelper include InPersonHelper diff --git a/spec/features/idv/steps/in_person_opt_in_ipp_spec.rb b/spec/features/idv/steps/in_person_opt_in_ipp_spec.rb index 14be95d501a..46cad186ab2 100644 --- a/spec/features/idv/steps/in_person_opt_in_ipp_spec.rb +++ b/spec/features/idv/steps/in_person_opt_in_ipp_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' require 'axe-rspec' -RSpec.describe 'In Person Proofing - Opt-in IPP ', js: true, allowed_extra_analytics: [:*] do +RSpec.describe 'In Person Proofing - Opt-in IPP ', js: true do include IdvStepHelper include SpAuthHelper include InPersonHelper diff --git a/spec/features/idv/steps/phone_step_spec.rb b/spec/features/idv/steps/phone_step_spec.rb index a9e25ad98c7..0cc49fbda6f 100644 --- a/spec/features/idv/steps/phone_step_spec.rb +++ b/spec/features/idv/steps/phone_step_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'idv phone step', :js, allowed_extra_analytics: [:*] do +RSpec.feature 'idv phone step', :js do include IdvStepHelper include IdvHelper diff --git a/spec/features/idv/steps/request_letter_step_spec.rb b/spec/features/idv/steps/request_letter_step_spec.rb index 1c144b7473e..20aed20e87d 100644 --- a/spec/features/idv/steps/request_letter_step_spec.rb +++ b/spec/features/idv/steps/request_letter_step_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'idv request letter step', allowed_extra_analytics: [:*] do +RSpec.feature 'idv request letter step' do include IdvStepHelper include OidcAuthHelper diff --git a/spec/features/idv/verify_by_mail_pending_spec.rb b/spec/features/idv/verify_by_mail_pending_spec.rb index dee065f1dff..b8251533908 100644 --- a/spec/features/idv/verify_by_mail_pending_spec.rb +++ b/spec/features/idv/verify_by_mail_pending_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'a user that is pending verify by mail', allowed_extra_analytics: [:*] do +RSpec.feature 'a user that is pending verify by mail' do include IdvStepHelper it 'requires them to enter code or cancel to enter the proofing flow' do diff --git a/spec/features/users/profile_recovery_for_gpo_verified_spec.rb b/spec/features/users/profile_recovery_for_gpo_verified_spec.rb index ab2b5f80fd1..6a99df19d6e 100644 --- a/spec/features/users/profile_recovery_for_gpo_verified_spec.rb +++ b/spec/features/users/profile_recovery_for_gpo_verified_spec.rb @@ -1,7 +1,6 @@ require 'rails_helper' -RSpec.feature 'Password recovery via personal key for a GPO-verified user', - allowed_extra_analytics: [:*] do +RSpec.feature 'Password recovery via personal key for a GPO-verified user' do include IdvStepHelper let(:new_password) { 'some really awesome new password' } diff --git a/spec/features/users/verify_profile_spec.rb b/spec/features/users/verify_profile_spec.rb index a834ab35928..c1a0ff7cf4d 100644 --- a/spec/features/users/verify_profile_spec.rb +++ b/spec/features/users/verify_profile_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.feature 'verify profile with OTP', allowed_extra_analytics: [:*] do +RSpec.feature 'verify profile with OTP' do include IdvStepHelper let(:user) { create(:user, :fully_registered) } diff --git a/spec/forms/gpo_verify_form_spec.rb b/spec/forms/gpo_verify_form_spec.rb index 5960098c39b..4aad640877a 100644 --- a/spec/forms/gpo_verify_form_spec.rb +++ b/spec/forms/gpo_verify_form_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe GpoVerifyForm, allowed_extra_analytics: [:*] do +RSpec.describe GpoVerifyForm do subject(:form) do GpoVerifyForm.new( user: user, diff --git a/spec/jobs/reports/combined_invoice_supplement_report_spec.rb b/spec/jobs/reports/combined_invoice_supplement_report_spec.rb deleted file mode 100644 index 9ef5279b3c6..00000000000 --- a/spec/jobs/reports/combined_invoice_supplement_report_spec.rb +++ /dev/null @@ -1,218 +0,0 @@ -require 'rails_helper' - -RSpec.describe Reports::CombinedInvoiceSupplementReport do - subject(:report) { Reports::CombinedInvoiceSupplementReport.new } - - let(:partner_account1) { create(:partner_account) } - let(:partner_account2) { create(:partner_account) } - let(:gtc1) do - create( - :iaa_gtc, - gtc_number: 'gtc1234', - partner_account: partner_account1, - start_date: iaa1_range.begin, - end_date: iaa1_range.end, - ) - end - - let(:gtc2) do - create( - :iaa_gtc, - gtc_number: 'gtc5678', - partner_account: partner_account2, - start_date: iaa2_range.begin, - end_date: iaa2_range.end, - ) - end - - let(:iaa_order1) do - build_iaa_order(order_number: 1, date_range: iaa1_range, iaa_gtc: gtc1) - end - let(:iaa_order2) do - build_iaa_order(order_number: 2, date_range: iaa2_range, iaa_gtc: gtc2) - end - - # Have to do this because of invalid check when building integration usages - let!(:iaa_orders) do - [ - iaa_order1, - iaa_order2, - ] - end - - let!(:iaa1_sp) do - create( - :service_provider, - iaa: iaa1, - iaa_start_date: iaa1_range.begin, - iaa_end_date: iaa1_range.end, - ) - end - - let!(:iaa2_sp1) do - create( - :service_provider, - iaa: iaa2, - iaa_start_date: iaa2_range.begin, - iaa_end_date: iaa2_range.end, - ) - end - let!(:iaa2_sp2) do - create( - :service_provider, - iaa: iaa2, - iaa_start_date: iaa2_range.begin, - iaa_end_date: iaa2_range.end, - ) - end - - let(:iaa1) { 'iaa1' } - let(:iaa1_range) { Date.new(2020, 4, 15)..Date.new(2021, 4, 14) } - let(:inside_iaa1) { iaa1_range.begin + 1.day } - - let(:iaa2) { 'iaa2' } - let(:iaa2_range) { Date.new(2020, 9, 1)..Date.new(2021, 8, 30) } - let(:inside_iaa2) { iaa2_range.begin + 1.day } - - describe '#perform' do - it 'is empty with no data' do - csv = CSV.parse(report.perform(Time.zone.today), headers: true) - expect(csv).to be_empty - end - - context 'with data' do - let(:user1) { create(:user) } - let(:user2) { create(:user) } - - before do - iaa_order1.integrations << build_integration( - issuer: iaa1_sp.issuer, - partner_account: partner_account1, - ) - iaa_order2.integrations << build_integration( - issuer: iaa2_sp1.issuer, - partner_account: partner_account2, - ) - iaa_order2.integrations << build_integration( - issuer: iaa2_sp2.issuer, - partner_account: partner_account2, - ) - iaa_order1.save - iaa_order2.save - - create( - :sp_return_log, - user_id: user1.id, - issuer: iaa1_sp.issuer, - ial: 1, - requested_at: inside_iaa1, - returned_at: inside_iaa1, - billable: true, - ) - - # 1 unique user in month 1 at IAA 2 sp 1 @ IAL 2 - create( - :sp_return_log, - user_id: user1.id, - ial: 2, - issuer: iaa2_sp1.issuer, - requested_at: inside_iaa2, - returned_at: inside_iaa2, - billable: true, - ) - - # 1 unique user in month 1 at IAA 2 sp 2 @ IAL 2 - create( - :sp_return_log, - user_id: user2.id, - ial: 2, - issuer: iaa2_sp2.issuer, - requested_at: inside_iaa2, - returned_at: inside_iaa2, - billable: true, - ) - end - - it 'generates a report by iaa + order number and issuer and year month' do - csv = CSV.parse(report.perform(Time.zone.today), headers: true) - - expect(csv.length).to eq(3) - - aggregate_failures do - row = csv.find { |r| r['issuer'] == iaa1_sp.issuer } - expect(row['iaa_order_number']).to eq('gtc1234-0001') - expect(row['iaa_start_date']).to eq('2020-04-15') - expect(row['iaa_end_date']).to eq('2021-04-14') - - expect(row['issuer']).to eq(iaa1_sp.issuer) - expect(row['friendly_name']).to eq(iaa1_sp.friendly_name) - - expect(row['year_month']).to eq('202004') - expect(row['year_month_readable']).to eq('April 2020') - - expect(row['iaa_ial1_unique_users'].to_i).to eq(1) - expect(row['iaa_ial2_unique_users'].to_i).to eq(0) - expect(row['iaa_ial1_plus_2_unique_users'].to_i).to eq(1) - expect(row['iaa_ial2_new_unique_users'].to_i).to eq(0) - - expect(row['issuer_ial1_total_auth_count'].to_i).to eq(1) - expect(row['issuer_ial2_total_auth_count'].to_i).to eq(0) - expect(row['issuer_ial1_plus_2_total_auth_count'].to_i).to eq(1) - - expect(row['issuer_ial1_unique_users'].to_i).to eq(1) - expect(row['issuer_ial2_unique_users'].to_i).to eq(0) - expect(row['issuer_ial1_plus_2_unique_users'].to_i).to eq(1) - expect(row['issuer_ial2_new_unique_users'].to_i).to eq(0) - end - - [iaa2_sp1, iaa2_sp2].each do |sp| - aggregate_failures do - row = csv.find { |r| r['issuer'] == sp.issuer } - - expect(row['iaa_order_number']).to eq('gtc5678-0002') - expect(row['iaa_start_date']).to eq('2020-09-01') - expect(row['iaa_end_date']).to eq('2021-08-30') - - expect(row['issuer']).to eq(sp.issuer) - expect(row['friendly_name']).to eq(sp.friendly_name) - - expect(row['year_month']).to eq('202009') - expect(row['year_month_readable']).to eq('September 2020') - - expect(row['iaa_ial1_unique_users'].to_i).to eq(0) - expect(row['iaa_ial2_unique_users'].to_i).to eq(2) - expect(row['iaa_ial1_plus_2_unique_users'].to_i).to eq(2) - expect(row['iaa_ial2_new_unique_users'].to_i).to eq(2) - - expect(row['issuer_ial1_total_auth_count'].to_i).to eq(0) - expect(row['issuer_ial2_total_auth_count'].to_i).to eq(1) - expect(row['issuer_ial1_plus_2_total_auth_count'].to_i).to eq(1) - - expect(row['issuer_ial1_unique_users'].to_i).to eq(0) - expect(row['issuer_ial2_unique_users'].to_i).to eq(1) - expect(row['issuer_ial1_plus_2_unique_users'].to_i).to eq(1) - expect(row['issuer_ial2_new_unique_users'].to_i).to eq(1) - end - end - end - end - end - - def build_iaa_order(order_number:, date_range:, iaa_gtc:) - create( - :iaa_order, - order_number: order_number, - start_date: date_range.begin, - end_date: date_range.end, - iaa_gtc: iaa_gtc, - ) - end - - def build_integration(issuer:, partner_account:) - create( - :integration, - issuer: issuer, - partner_account: partner_account, - ) - end -end diff --git a/spec/lib/action_account_spec.rb b/spec/lib/action_account_spec.rb index 2ff93a4206d..e9bacd37a78 100644 --- a/spec/lib/action_account_spec.rb +++ b/spec/lib/action_account_spec.rb @@ -204,6 +204,10 @@ subject(:result) { subtask.run(args:, config:) } it 'Pass a user that has a pending review', aggregate_failures: true do + expect(UserAlerts::AlertUserAboutAccountVerified).to receive(:call).with( + profile: user.pending_profile, + ) + profile_fraud_review_pending_at = user.pending_profile.fraud_review_pending_at # rubocop:disable Layout/LineLength diff --git a/spec/models/service_provider_spec.rb b/spec/models/service_provider_spec.rb index cac4ad7d3b4..ba79ea601f9 100644 --- a/spec/models/service_provider_spec.rb +++ b/spec/models/service_provider_spec.rb @@ -7,6 +7,7 @@ subject { service_provider } it { is_expected.to belong_to(:agency) } + it do is_expected.to have_many(:identities). inverse_of(:service_provider_record). @@ -86,6 +87,7 @@ allow(IdentityConfig.store).to receive(:biometric_ial_enabled). and_return(true) end + context 'when the service provider is in the allowed list' do before do allow(IdentityConfig.store).to receive(:allowed_biometric_ial_providers). @@ -102,16 +104,19 @@ allow(IdentityConfig.store).to receive(:allowed_biometric_ial_providers). and_return([]) end + it 'does not allow the service provider to use biometric IALs' do expect(service_provider.biometric_ial_allowed?).to be(false) end end end + context 'when the biometric ial feature is disabled' do before do allow(IdentityConfig.store).to receive(:biometric_ial_enabled). and_return(false) end + context 'when the service provider is in the allowed list' do before do allow(IdentityConfig.store).to receive(:allowed_biometric_ial_providers). @@ -125,6 +130,57 @@ end end + describe '#semantic_authn_contexts_allowed?' do + context 'when the semantic authn contexts feature is enabled' do + before do + allow(IdentityConfig.store). + to receive(:feature_valid_authn_contexts_semantic_enabled). + and_return(true) + end + + context 'when the service provider is in the allowed list' do + before do + allow(IdentityConfig.store). + to receive(:allowed_valid_authn_contexts_semantic_providers). + and_return([service_provider.issuer]) + end + + it 'allows the service provider to use semantic ACRs' do + expect(service_provider.semantic_authn_contexts_allowed?).to be(true) + end + end + + context 'when the service provider is not in the allowed list' do + before do + allow(IdentityConfig.store).to receive(:allowed_valid_authn_contexts_semantic_providers). + and_return([]) + end + + it 'does not allow the service provider to use semantic ACRs' do + expect(service_provider.semantic_authn_contexts_allowed?).to be(false) + end + end + end + + context 'when the semantic ACRs feature is disabled' do + before do + allow(IdentityConfig.store).to receive(:feature_valid_authn_contexts_semantic_enabled). + and_return(false) + end + + context 'when the service provider is in the allowed list' do + before do + allow(IdentityConfig.store).to receive(:allowed_valid_authn_contexts_semantic_providers). + and_return([service_provider.issuer]) + end + + it 'does not allow the service provider to use semantic ACRs' do + expect(service_provider.semantic_authn_contexts_allowed?).to be(false) + end + end + end + end + describe '#ssl_certs' do context 'with an empty string plural cert' do let(:service_provider) { build(:service_provider, certs: ['']) } diff --git a/spec/services/attribute_asserter_spec.rb b/spec/services/attribute_asserter_spec.rb index f8e656f3e75..aa13f7f3885 100644 --- a/spec/services/attribute_asserter_spec.rb +++ b/spec/services/attribute_asserter_spec.rb @@ -23,6 +23,7 @@ ial: service_provider_ial, default_aal: service_provider_aal, metadata: {}, + semantic_authn_contexts_allowed?: false, ) end @@ -241,6 +242,7 @@ context 'when the user has been proofed with biometric' do let(:user) { create(:profile, :active, :verified, idv_level: :in_person).user } + before do user.identities << identity subject.build @@ -324,6 +326,7 @@ context 'the service provider is ial2' do let(:service_provider_ial) { 2 } + it 'includes verified_at' do expect(user.asserted_attributes.keys).to eq %i[uuid email verified_at aal ial] end @@ -601,6 +604,7 @@ context 'when the user has been proofed with biometric' do let(:user) { biometric_verified_user } + before do user.identities << identity subject.build @@ -636,6 +640,7 @@ context 'when the user has been proofed with biometric comparison' do let(:user) { biometric_verified_user } + before do user.identities << identity subject.build @@ -729,6 +734,7 @@ user_session: user_session, ) end + before do user.identities << identity allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle). @@ -762,6 +768,7 @@ user_session: user_session, ) end + before do user.identities << identity allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle). @@ -797,6 +804,7 @@ user_session: user_session, ) end + before do user.identities << identity allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle). @@ -830,6 +838,7 @@ user_session: user_session, ) end + before do user.identities << identity allow(service_provider.metadata).to receive(:[]).with(:attribute_bundle). @@ -860,6 +869,7 @@ describe 'when no aal requested' do context 'default_aal is nil' do let(:authn_context) { [] } + it 'asserts default aal' do expect(get_asserted_attribute(user, :aal)).to eq( Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF, diff --git a/spec/services/authn_context_resolver_spec.rb b/spec/services/authn_context_resolver_spec.rb index 3148a195f5b..59ae6de9ed0 100644 --- a/spec/services/authn_context_resolver_spec.rb +++ b/spec/services/authn_context_resolver_spec.rb @@ -301,6 +301,7 @@ acr_values: acr_values, ) end + let(:result) { subject.result } context 'if IAL ACR value is present' do @@ -417,10 +418,299 @@ context 'when the user has not yet been verified' do let(:user) { build(:user) } + + it 'asserts biometric comparison' do + expect(result.identity_proofing?).to be true + expect(result.biometric_comparison?).to be true + expect(result.aal2?).to be true + end + end + end + end + end + end + + context 'when resolving semantic acr_values' do + before do + allow(IdentityConfig.store). + to receive(:feature_valid_authn_contexts_semantic_enabled). + and_return(true) + allow_any_instance_of(ServiceProvider). + to receive(:semantic_authn_contexts_allowed?). + and_return(true) + + stub_const( + 'Saml::Idp::Constants::VALID_AUTHN_CONTEXTS', + IdentityConfig.store.valid_authn_contexts_semantic, + ) + end + + context 'when no semantic ACR present' do + let(:user) { build(:user) } + + it 'does not resolve legacy ACRs to semantic ACRs' do + acr_values = [ + Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + ] + + resolver = AuthnContextResolver.new( + user: user, + service_provider: nil, + vtr: nil, + acr_values: acr_values.join(' '), + ) + result = resolver.result + + expect(resolver.asserted_ial_acr).to eq(Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF) + expect(result.component_names).to eq(acr_values) + expect(result.to_h).to include( + aal2?: false, + biometric_comparison?: false, + enhanced_ipp?: false, + hspd12?: false, + ialmax?: false, + identity_proofing?: false, + phishing_resistant?: false, + ) + end + end + + context 'with no service provider' do + it 'parses an ACR value into requirements' do + acr_values = [ + 'http://idmanagement.gov/ns/assurance/aal/2', + Saml::Idp::Constants::IAL_AUTH_ONLY_ACR, + ] + + resolver = AuthnContextResolver.new( + user: user, + service_provider: nil, + vtr: nil, + acr_values: acr_values.join(' '), + ) + result = resolver.result + + expect(result.component_names).to eq(acr_values) + expect(result.aal2?).to eq(true) + expect(result.phishing_resistant?).to eq(false) + expect(result.hspd12?).to eq(false) + expect(result.identity_proofing?).to eq(false) + expect(result.biometric_comparison?).to eq(false) + expect(result.ialmax?).to eq(false) + expect(result.enhanced_ipp?).to eq(false) + end + + it 'properly parses an ACR value without an AAL ACR' do + acr_values = [ + Saml::Idp::Constants::IAL_AUTH_ONLY_ACR, + ] + + resolver = AuthnContextResolver.new( + user: user, + service_provider: nil, + vtr: nil, + acr_values: acr_values.join(' '), + ) + result = resolver.result + + expect(result.component_names).to eq(acr_values) + expect(result.aal2?).to eq(false) + expect(result.phishing_resistant?).to eq(false) + expect(result.hspd12?).to eq(false) + expect(result.identity_proofing?).to eq(false) + expect(result.biometric_comparison?).to eq(false) + expect(result.ialmax?).to eq(false) + expect(result.enhanced_ipp?).to eq(false) + end + + it 'properly parses an ACR value without an IAL ACR' do + acr_values = [ + 'http://idmanagement.gov/ns/assurance/aal/2', + ] + resolver = AuthnContextResolver.new( + user: user, + service_provider: nil, + vtr: nil, + acr_values: acr_values.join(' '), + ) + result = resolver.result + expect(result.component_names).to eq(acr_values) + expect(result.aal2?).to eq(true) + expect(result.phishing_resistant?).to eq(false) + expect(result.hspd12?).to eq(false) + expect(result.identity_proofing?).to eq(false) + expect(result.biometric_comparison?).to eq(false) + expect(result.ialmax?).to eq(false) + expect(result.enhanced_ipp?).to eq(false) + end + end + + context 'with an IAL2 service provider' do + let(:service_provider) { create(:service_provider, :idv) } + subject do + AuthnContextResolver.new( + user: user, + service_provider: service_provider, + vtr: nil, + acr_values: acr_values&.join(' '), + ) + end + + let(:result) { subject.result } + + context 'if IAL ACR value is present' do + let(:acr_values) do + [ + 'http://idmanagement.gov/ns/assurance/ial/1', + 'http://idmanagement.gov/ns/assurance/aal/1', + ] + end + + it 'uses the IAL ACR if one is present' do + expect(result.identity_proofing?).to be false + expect(result.aal2?).to be false + end + end + + context 'if multiple IAL ACR values are present' do + let(:acr_values) do + [ + 'http://idmanagement.gov/ns/assurance/ial/1', + 'urn:acr.login.gov:verified', + 'http://idmanagement.gov/ns/assurance/aal/1', + ] + end + + it 'uses the highest IAL ACR if one is present' do + expect(result.identity_proofing?).to be true + expect(result.aal2?).to be true + end + end + + context 'if No IAL ACR is present' do + let(:acr_values) do + [ + 'http://idmanagement.gov/ns/assurance/aal/1', + ] + end + + context 'when user is not verified' do + let(:user) { build(:user, :fully_registered) } + + it "asserts #{Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF}" do + expect(subject.asserted_ial_acr). + to eq(Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF) + expect(result.identity_proofing?).to be true + expect(result.aal2?).to be true + end + end + + context 'when user is verified' do + let(:user) { build(:user, :proofed) } + + it "asserts #{Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF}" do + expect(subject.asserted_ial_acr). + to eq(Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF) + end + end + end + + context 'if requesting biometric comparison' do + let(:bio_value) { 'required' } + let(:acr_values) do + [ + "urn:acr.login.gov:verified-facial-match-#{bio_value}", + 'http://idmanagement.gov/ns/assurance/aal/1', + ] + end + + before do + allow_any_instance_of(ServiceProvider). + to receive(:biometric_ial_allowed?). + and_return(true) + end + + context 'with biometric comparison is required' do + context 'when user is not verified' do + it "asserts the resolved IAL as #{Saml::Idp::Constants::IAL_AUTH_ONLY_ACR}" do + expect(subject.asserted_ial_acr). + to eq(Saml::Idp::Constants::IAL_AUTH_ONLY_ACR) + end + + it 'sets biometric_comparison to true' do + expect(result.identity_proofing?).to be true + expect(result.biometric_comparison?).to be true + expect(result.aal2?).to be true + expect(result.two_pieces_of_fair_evidence?).to be true + expect(result.ialmax?).to be false + end + end + + context 'when the user is already verified' do + context 'without biometric comparison' do + let(:user) { build(:user, :proofed) } + + it 'asserts biometric_comparison as true' do + expect(result.identity_proofing?).to be true + expect(result.biometric_comparison?).to be true + expect(result.aal2?).to be true + expect(result.two_pieces_of_fair_evidence?).to be true + expect(result.ialmax?).to be false + end + end + + context 'with biometric comparison' do + let(:user) { build(:user, :proofed_with_selfie) } + + it 'asserts biometric comparison' do + expect(result.identity_proofing?).to be true + expect(result.biometric_comparison?).to be true + expect(result.two_pieces_of_fair_evidence?).to be true + expect(result.aal2?).to be true + expect(result.ialmax?).to be false + end + end + end + end + + context 'with biometric comparison is preferred' do + let(:bio_value) { 'preferred' } + + context 'when the user is already verified' do + context 'without biometric comparison' do + let(:user) { build(:user, :proofed) } + + it 'falls back on proofing without biometric comparison' do + expect(result.identity_proofing?).to be true + expect(result.biometric_comparison?).to be false + expect(result.two_pieces_of_fair_evidence?).to be false + expect(result.aal2?).to be true + expect(result.ialmax?).to be false + end + end + + context 'with biometric comparison' do + let(:user) { build(:user, :proofed_with_selfie) } + + it 'asserts biometric comparison' do + expect(result.identity_proofing?).to be true + expect(result.biometric_comparison?).to be true + expect(result.two_pieces_of_fair_evidence?).to be true + expect(result.aal2?).to be true + expect(result.ialmax?).to be false + end + end + end + + context 'when the user has not yet been verified' do + let(:user) { build(:user) } + it 'asserts biometric comparison' do expect(result.identity_proofing?).to be true expect(result.biometric_comparison?).to be true + expect(result.two_pieces_of_fair_evidence?).to be true expect(result.aal2?).to be true + expect(result.ialmax?).to be false end end end diff --git a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb index 821fefd18f1..8206bbe528f 100644 --- a/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb +++ b/spec/services/doc_auth/mock/doc_auth_mock_client_spec.rb @@ -258,7 +258,7 @@ ) expect(response.selfie_check_performed?).to be(true) - expect(response.extra).to have_key(:portrait_match_results) + expect(response.extra[:portrait_match_results]).to be_present end it 'returns selfie status with default setting' do @@ -271,7 +271,7 @@ expect(response.selfie_check_performed?).to be(true) expect(response.selfie_status).to eq(:success) - expect(response.extra).to_not have_key(:portrait_match_results) + expect(response.extra[:portrait_match_results]).to be_nil end end @@ -291,7 +291,7 @@ ) expect(response.selfie_check_performed?).to be(false) - expect(response.extra).not_to have_key(:portrait_match_results) + expect(response.extra[:portrait_match_results]).to be_nil end end end diff --git a/spec/services/doc_auth/mock/result_response_spec.rb b/spec/services/doc_auth/mock/result_response_spec.rb index 8398f56c89d..7988ac33622 100644 --- a/spec/services/doc_auth/mock/result_response_spec.rb +++ b/spec/services/doc_auth/mock/result_response_spec.rb @@ -323,6 +323,7 @@ classification_info: {}, workflow: 'test_non_liveness_workflow', liveness_checking_required: false, + portrait_match_results: nil, ) expect(response.doc_auth_success?).to eq(true) expect(response.selfie_status).to eq(:not_processed) @@ -392,11 +393,33 @@ expect(response.pii_from_doc).to eq(nil) expect(response.attention_with_barcode?).to eq(false) expect(response.extra).to eq( - doc_auth_result: DocAuth::LexisNexis::ResultCodes::CAUTION.name, + doc_auth_result: DocAuth::LexisNexis::ResultCodes::FAILED.name, billed: true, - classification_info: {}, + classification_info: nil, liveness_checking_required: false, workflow: 'test_non_liveness_workflow', + portrait_match_results: nil, + alert_failure_count: 1, + liveness_enabled: false, + vendor: 'Mock', + processed_alerts: { + failed: [{ name: '2D Barcode Read', result: 'Failed' }], + passed: [], + }, + image_metrics: { + back: { + 'GlareMetric' => 100, + 'HorizontalResolution' => 100, + 'SharpnessMetric' => 100, + 'VerticalResolution' => 600, + }, + front: { + 'GlareMetric' => 100, + 'HorizontalResolution' => 600, + 'SharpnessMetric' => 100, + 'VerticalResolution' => 600, + }, + }, ) end end @@ -421,9 +444,31 @@ expect(response.extra).to eq( doc_auth_result: DocAuth::LexisNexis::ResultCodes::FAILED.name, billed: true, - classification_info: {}, + classification_info: nil, liveness_checking_required: false, workflow: 'test_non_liveness_workflow', + portrait_match_results: nil, + alert_failure_count: 1, + liveness_enabled: false, + vendor: 'Mock', + processed_alerts: { + failed: [{ name: '2D Barcode Read', result: 'Failed' }], + passed: [], + }, + image_metrics: { + back: { + 'GlareMetric' => 100, + 'HorizontalResolution' => 600, + 'SharpnessMetric' => 100, + 'VerticalResolution' => 600, + }, + front: { + 'GlareMetric' => 100, + 'HorizontalResolution' => 600, + 'SharpnessMetric' => 100, + 'VerticalResolution' => 600, + }, + }, ) end end @@ -480,6 +525,7 @@ classification_info: {}, liveness_checking_required: false, workflow: 'test_non_liveness_workflow', + portrait_match_results: nil, ) end end @@ -788,7 +834,7 @@ it 'returns the expected values' do expect(response.selfie_check_performed?).to eq(false) - expect(response.extra).not_to have_key(:portrait_match_results) + expect(response.extra[:portrait_match_results]).to be_nil expect(response.doc_auth_success?).to eq(true) expect(response.selfie_status).to eq(:not_processed) expect(response.extra[:liveness_checking_required]).to eq(false) diff --git a/spec/services/saml_request_validator_spec.rb b/spec/services/saml_request_validator_spec.rb index b8078956318..427827ae380 100644 --- a/spec/services/saml_request_validator_spec.rb +++ b/spec/services/saml_request_validator_spec.rb @@ -24,6 +24,7 @@ end let(:use_vot_in_sp_requests) { true } + before do allow(IdentityConfig.store).to receive( :use_vot_in_sp_requests, @@ -43,6 +44,7 @@ context 'ialmax authncontext and ialmax provider' do let(:authn_context) { [Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF] } + before do expect(IdentityConfig.store).to receive(:allowed_ialmax_providers) { [sp.issuer] } end @@ -59,6 +61,7 @@ context 'valid authn context and invalid sp and authorized nameID format' do let(:sp) { ServiceProvider.find_by(issuer: 'foo') } + it 'returns FormResponse with success: false' do errors = { service_provider: [t('errors.messages.unauthorized_service_provider')], @@ -75,6 +78,7 @@ context 'valid authn context and unauthorized nameid format' do let(:name_id_format) { Saml::Idp::Constants::NAME_ID_FORMAT_EMAIL } + it 'returns FormResponse with success: false' do errors = { nameid_format: [t('errors.messages.unauthorized_nameid_format')], @@ -92,7 +96,9 @@ context 'valid authn context and authorized email nameid format for SP' do let(:sp) { ServiceProvider.find_by(issuer: 'https://rp1.serviceprovider.com/auth/saml/metadata') } let(:name_id_format) { Saml::Idp::Constants::NAME_ID_FORMAT_EMAIL } + before { sp.update!(email_nameid_format_allowed: true) } + it 'returns FormResponse with success: true' do expect(response.to_h).to include( success: true, @@ -103,7 +109,9 @@ context 'ial2 authn context and ial2 sp' do let(:authn_context) { [Saml::Idp::Constants::IAL2_AUTHN_CONTEXT_CLASSREF] } + before { sp.update!(ial: 2) } + it 'returns FormResponse with success: true for ial2 on ial:2 sp' do expect(response.to_h).to include( success: true, @@ -175,6 +183,7 @@ context 'invalid authn context and valid sp and authorized nameID format' do context 'unknown auth context' do let(:authn_context) { ['IAL1'] } + it 'returns FormResponse with success: false' do errors = { authn_context: [t('errors.messages.unauthorized_authn_context')], @@ -228,6 +237,8 @@ context "when the IAL requested is #{biometric_ial}" do context 'when the service provider is allowed to use biometric ials' do + let(:sp) { create(:service_provider, :idv) } + before do allow_any_instance_of(ServiceProvider).to receive(:biometric_ial_allowed?). and_return(true) @@ -269,6 +280,57 @@ it_behaves_like 'allows biometric IAL only if sp is authorized', Saml::Idp::Constants::IAL2_BIO_PREFERRED_AUTHN_CONTEXT_CLASSREF + + shared_examples 'allows semantic IAL only if sp is authorized' do |semantic_ial| + let(:authn_context) { [semantic_ial] } + + context "when the IAL requested is #{semantic_ial}" do + context 'when the service provider is allowed to use semantic ials' do + let(:sp) { create(:service_provider, :idv) } + + before do + allow_any_instance_of(ServiceProvider). + to receive(:semantic_authn_contexts_allowed?). + and_return(true) + end + + it 'returns a successful response' do + expect(response.to_h).to include( + success: true, + errors: {}, + **extra, + ) + end + end + + context 'when the service provider is not allowed to use semantic ials' do + before do + allow_any_instance_of(ServiceProvider). + to receive(:semantic_authn_contexts_allowed?). + and_return(false) + end + + it 'fails with an unauthorized error' do + errors = { + authn_context: [t('errors.messages.unauthorized_authn_context')], + } + + expect(response.to_h).to include( + success: false, + errors: errors, + error_details: hash_including(*errors.keys), + **extra, + ) + end + end + end + end + + it_behaves_like 'allows semantic IAL only if sp is authorized', + Saml::Idp::Constants::IAL_VERIFIED_ACR + + it_behaves_like 'allows semantic IAL only if sp is authorized', + Saml::Idp::Constants::IAL_AUTH_ONLY_ACR end context 'invalid authn context and invalid sp and authorized nameID format' do @@ -292,6 +354,7 @@ context 'valid authn context and sp and unauthorized nameID format' do let(:name_id_format) { Saml::Idp::Constants::NAME_ID_FORMAT_EMAIL } + it 'returns FormResponse with success: false with unauthorized nameid format' do errors = { nameid_format: [t('errors.messages.unauthorized_nameid_format')], @@ -320,6 +383,7 @@ context 'valid VTR for identity proofing with authorized SP for identity proofing' do let(:authn_context) { ['C1.P1'] } + before { sp.update!(ial: 2) } it 'returns FormResponse with success true' do @@ -333,6 +397,7 @@ context 'valid VTR for identity proofing with unauthorized SP for identity proofing' do let(:authn_context) { ['C1.P1'] } + before { sp.update!(ial: 1) } it 'returns FormResponse with success false' do @@ -351,6 +416,7 @@ context 'multiple VTR for identity proofing with unauthorized SP for identity proofing' do let(:authn_context) { ['C1', 'C1.P1'] } + before { sp.update!(ial: 1) } it 'returns FormResponse with success false' do diff --git a/spec/services/user_alerts/alert_user_about_account_verified_spec.rb b/spec/services/user_alerts/alert_user_about_account_verified_spec.rb index 44445fd6b4e..41ab2c4f0a6 100644 --- a/spec/services/user_alerts/alert_user_about_account_verified_spec.rb +++ b/spec/services/user_alerts/alert_user_about_account_verified_spec.rb @@ -2,34 +2,49 @@ RSpec.describe UserAlerts::AlertUserAboutAccountVerified do describe '#call' do - let(:user) { create(:user, :fully_registered) } - let(:device) { create(:device, user: user) } - let(:date_time) { Time.zone.now } + let(:user) { profile.user } + let(:profile) do + create( + :profile, + :active, + initiating_service_provider: service_provider, + ) + end + let(:service_provider) { create(:service_provider) } it 'sends an email to all confirmed email addresses' do create_list(:email_address, 2, user: user) create(:email_address, user: user, confirmed_at: nil) confirmed_email_addresses = user.confirmed_email_addresses - described_class.call( - user: user, - date_time: date_time, - sp_name: '', - ) + described_class.call(profile: profile) expect_delivered_email_count(3) expect_delivered_email( to: [confirmed_email_addresses[0].email], - subject: t('user_mailer.account_verified.subject', sp_name: ''), + subject: t('user_mailer.account_verified.subject', sp_name: service_provider.friendly_name), ) expect_delivered_email( to: [confirmed_email_addresses[1].email], - subject: t('user_mailer.account_verified.subject', sp_name: ''), + subject: t('user_mailer.account_verified.subject', sp_name: service_provider.friendly_name), ) expect_delivered_email( to: [confirmed_email_addresses[2].email], - subject: t('user_mailer.account_verified.subject', sp_name: ''), + subject: t('user_mailer.account_verified.subject', sp_name: service_provider.friendly_name), ) end + + context 'when no service provider initiated the proofing event' do + let(:service_provider) { nil } + + it 'sends the email with Login.gov as the initiating service provider' do + described_class.call(profile: profile) + + expect_delivered_email( + to: [user.confirmed_email_addresses.first.email], + subject: t('user_mailer.account_verified.subject', sp_name: APP_NAME), + ) + end + end end end diff --git a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb index e1b1ed9c9dd..bfd985a06bb 100644 --- a/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb +++ b/spec/services/usps_in_person_proofing/enrollment_helper_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe UspsInPersonProofing::EnrollmentHelper, allowed_extra_analytics: [:*] do +RSpec.describe UspsInPersonProofing::EnrollmentHelper do include UspsIppHelper let(:usps_mock_fallback) { false } diff --git a/spec/services/vot/parser_spec.rb b/spec/services/vot/parser_spec.rb index 70da8157114..3ea2f312878 100644 --- a/spec/services/vot/parser_spec.rb +++ b/spec/services/vot/parser_spec.rb @@ -66,8 +66,8 @@ vector_of_trust = 'C1.C2.Xx' expect { Vot::Parser.new(vector_of_trust:).parse }.to raise_exception( - Vot::Parser::ParseException, - 'C1.C2.Xx contains unkown component Xx', + Vot::Parser::UnsupportedComponentsException, + /'Xx'$/, ) end end @@ -76,8 +76,7 @@ it 'raises an exception' do vector_of_trust = 'C1.C1' expect { Vot::Parser.new(vector_of_trust:).parse }.to raise_exception( - Vot::Parser::ParseException, - 'C1.C1 contains duplicate components', + Vot::Parser::DuplicateComponentsException, ) end