From 290d9094064f7eb4fb26d95a3e99a5453260e5f2 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Thu, 5 Dec 2024 10:01:42 -0800 Subject: [PATCH 01/14] Reload all AB tests after running AB test specs (#11583) Because of how this spec tests initializers, it can leave weirdly configured AB tests around after it runs. This is usually not a problem, but can introduce flakiness in CI. [skip changelog] --- spec/config/initializers/ab_tests_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/config/initializers/ab_tests_spec.rb b/spec/config/initializers/ab_tests_spec.rb index 724b71e32df..fc2c57eb656 100644 --- a/spec/config/initializers/ab_tests_spec.rb +++ b/spec/config/initializers/ab_tests_spec.rb @@ -3,6 +3,10 @@ RSpec.describe AbTests do include AbTestsHelper + after :suite do + reload_ab_tests + end + describe '#all' do it 'returns all registered A/B tests' do expect(AbTests.all.values).to all(be_kind_of(AbTest)) From d461b1b4d6b52f5a71a496676ff5bf381c77dad8 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:50:03 -0500 Subject: [PATCH 02/14] LG-15197: Omit policy_details_api from ThreatMetrix response body logging (#11601) changelog: Internal, Anti-Fraud, Omit policy_details_api from ThreatMetrix response body logging --- app/services/proofing/lexis_nexis/ddp/response_redacter.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/services/proofing/lexis_nexis/ddp/response_redacter.rb b/app/services/proofing/lexis_nexis/ddp/response_redacter.rb index 75dda7dc8cd..721a01738a4 100644 --- a/app/services/proofing/lexis_nexis/ddp/response_redacter.rb +++ b/app/services/proofing/lexis_nexis/ddp/response_redacter.rb @@ -152,7 +152,6 @@ class ResponseRedacter national_id_worst_score org_id policy - policy_details_api policy_engine_version policy_score page_time_on From 89afb54a4f5b4b50d88e2ce83df83d6678e11061 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Thu, 5 Dec 2024 14:32:51 -0500 Subject: [PATCH 03/14] LG-15121 Add a `post_idv_followup_url` configuration to service providers (#11591) We have been working through issues with a service provider who has observed that users do not return to their SP after entering their mailed code or passing fraud review. We identified an opportunity to provide users with a direct link back to the SP to re-initiate their session when they complete out-of-band proofing. This commit adds a `post_idv_followup_url` to the SP configuration for this purpose. This URL is used in the `account_verified` email if it is present. In future changes we plan to add a CTA after the user enters a mailed code and to the account page if the user has not connected their account. This change should not impact SPs that do not have a `post_idv_followup_url` configured. [skip changelog] --- ...account_verified_cta_visited_controller.rb | 3 +- .../idv/account_verified_email_presenter.rb | 8 ++--- app/services/sp_return_url_resolver.rb | 4 +++ config/service_providers.localdev.yml | 1 + ...t_idv_follow_up_url_to_service_provider.rb | 5 +++ db/schema.rb | 3 +- ...nt_verified_cta_visited_controller_spec.rb | 7 +++-- .../account_verified_email_presenter_spec.rb | 7 +++-- spec/services/sp_return_url_resolver_spec.rb | 31 +++++++++++++++++++ 9 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 db/primary_migrate/20241203163014_add_post_idv_follow_up_url_to_service_provider.rb diff --git a/app/controllers/idv/account_verified_cta_visited_controller.rb b/app/controllers/idv/account_verified_cta_visited_controller.rb index 550e65ea2c1..b058db1b588 100644 --- a/app/controllers/idv/account_verified_cta_visited_controller.rb +++ b/app/controllers/idv/account_verified_cta_visited_controller.rb @@ -22,7 +22,8 @@ def redirect_url if issuer.blank? root_url else - sp_return_url_resolver&.return_to_sp_url + sp_return_url_resolver&.post_idv_follow_up_url || + sp_return_url_resolver&.return_to_sp_url end end diff --git a/app/presenters/idv/account_verified_email_presenter.rb b/app/presenters/idv/account_verified_email_presenter.rb index 46477f0f201..a7a5748ce40 100644 --- a/app/presenters/idv/account_verified_email_presenter.rb +++ b/app/presenters/idv/account_verified_email_presenter.rb @@ -16,7 +16,7 @@ def service_provider end def show_cta? - !service_provider || service_provider_homepage_url.present? + !service_provider || service_provider_post_idv_follow_up_url.present? end def sign_in_url @@ -32,11 +32,11 @@ def sign_in_url end def displayed_sign_in_url - service_provider_homepage_url || root_url + service_provider_post_idv_follow_up_url || root_url end - def service_provider_homepage_url - sp_return_url_resolver.homepage_url if service_provider + def service_provider_post_idv_follow_up_url + sp_return_url_resolver.post_idv_follow_up_url if service_provider end def sp_name diff --git a/app/services/sp_return_url_resolver.rb b/app/services/sp_return_url_resolver.rb index dbed6e0f4b4..0e2f2e3bfd8 100644 --- a/app/services/sp_return_url_resolver.rb +++ b/app/services/sp_return_url_resolver.rb @@ -24,6 +24,10 @@ def homepage_url service_provider.return_to_sp_url end + def post_idv_follow_up_url + service_provider.post_idv_follow_up_url || homepage_url + end + private def inferred_redirect_url diff --git a/config/service_providers.localdev.yml b/config/service_providers.localdev.yml index 9b905e8ad0f..2a69dd342e1 100644 --- a/config/service_providers.localdev.yml +++ b/config/service_providers.localdev.yml @@ -217,6 +217,7 @@ test: assertion_consumer_logout_service_url: '' ial: 2 allow_prompt_login: true + post_idv_follow_up_url: http://localhost/post_idv_follow_up_url 'urn:gov:gsa:openidconnect:sp:server_ial1': agency_id: 2 diff --git a/db/primary_migrate/20241203163014_add_post_idv_follow_up_url_to_service_provider.rb b/db/primary_migrate/20241203163014_add_post_idv_follow_up_url_to_service_provider.rb new file mode 100644 index 00000000000..44440e44430 --- /dev/null +++ b/db/primary_migrate/20241203163014_add_post_idv_follow_up_url_to_service_provider.rb @@ -0,0 +1,5 @@ +class AddPostIdvFollowUpUrlToServiceProvider < ActiveRecord::Migration[7.2] + def change + add_column :service_providers, :post_idv_follow_up_url, :string, comment: 'sensitive=false' + end +end diff --git a/db/schema.rb b/db/schema.rb index 0c4f93cf8d4..135f2b0231c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_11_15_215510) do +ActiveRecord::Schema[7.2].define(version: 2024_12_03_163014) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "pg_stat_statements" @@ -551,6 +551,7 @@ t.boolean "use_legacy_name_id_behavior", default: false, comment: "sensitive=false" t.boolean "irs_attempts_api_enabled", comment: "sensitive=false" t.boolean "in_person_proofing_enabled", default: false, comment: "sensitive=false" + t.string "post_idv_follow_up_url", comment: "sensitive=false" t.index ["issuer"], name: "index_service_providers_on_issuer", unique: true end diff --git a/spec/controllers/idv/account_verified_cta_visited_controller_spec.rb b/spec/controllers/idv/account_verified_cta_visited_controller_spec.rb index 338e3b1b709..8600a739930 100644 --- a/spec/controllers/idv/account_verified_cta_visited_controller_spec.rb +++ b/spec/controllers/idv/account_verified_cta_visited_controller_spec.rb @@ -15,12 +15,12 @@ describe 'GET #show' do subject(:action) { get :show, params: } - context 'issuer provided and return_to_sp_url present' do + context 'issuer provided and post_idv_follow_up_url present' do let(:service_provider) do build( :service_provider, issuer: 'urn:my:awesome:issuer', - return_to_sp_url: 'https://some-sp.com', + post_idv_follow_up_url: 'https://some-sp.com', ) end @@ -28,7 +28,7 @@ { issuer: 'urn:my:awesome:issuer', campaign_id: '123234234' } end - it 'redirects to the service provider home page and logs event' do + it 'redirects to the service provider follow_up url and logs event' do action aggregate_failures 'verify response' do @@ -50,6 +50,7 @@ :service_provider, issuer: 'urn:my:awesome:issuer', return_to_sp_url: nil, + post_idv_follow_up_url: nil, acs_url: nil, redirect_uris: nil, ) diff --git a/spec/presenters/idv/account_verified_email_presenter_spec.rb b/spec/presenters/idv/account_verified_email_presenter_spec.rb index f31f64dc681..27d34501786 100644 --- a/spec/presenters/idv/account_verified_email_presenter_spec.rb +++ b/spec/presenters/idv/account_verified_email_presenter_spec.rb @@ -57,11 +57,12 @@ end context 'where there is a service provider' do - context 'when the service provider has no return URL' do + context 'when the service provider has no post-IdV follow-up URL' do let(:service_provider) do create( :service_provider, issuer: 'urn:my:awesome:sp', + post_idv_follow_up_url: nil, return_to_sp_url: nil, friendly_name: 'My Awesome SP', ) @@ -101,12 +102,12 @@ end end - context 'when the service provider does have a return URL' do + context 'when the service provider does have a post-IdV follow-up URL' do let(:service_provider) do create( :service_provider, issuer: 'urn:my:awesome:sp', - return_to_sp_url: 'https://www.mysp.com', + post_idv_follow_up_url: 'https://www.mysp.com', friendly_name: 'My Awesome SP', ) end diff --git a/spec/services/sp_return_url_resolver_spec.rb b/spec/services/sp_return_url_resolver_spec.rb index 30fc0aa8cc0..3181cb88723 100644 --- a/spec/services/sp_return_url_resolver_spec.rb +++ b/spec/services/sp_return_url_resolver_spec.rb @@ -140,4 +140,35 @@ end end end + + describe '#post_idv_follow_up_url' do + let(:return_to_sp_url) { nil } + let(:sp_post_idv_follow_up_url) { nil } + let(:sp) do + build( + :service_provider, + return_to_sp_url: return_to_sp_url, + post_idv_follow_up_url: sp_post_idv_follow_up_url, + ) + end + let(:instance) { described_class.new(service_provider: sp) } + subject(:post_idv_follow_up_url) { instance.post_idv_follow_up_url } + + context 'with not homepage url or follow_up url configured' do + it { expect(post_idv_follow_up_url).to be_nil } + end + + context 'with a homepage url configured' do + let(:return_to_sp_url) { 'https://sp.gov/return_to_sp' } + + it { expect(post_idv_follow_up_url).to eq(return_to_sp_url) } + end + + context 'with a follow_up url configured' do + let(:return_to_sp_url) { 'https://sp.gov/return_to_sp' } + let(:sp_post_idv_follow_up_url) { 'https://sp.gov/follow_up' } + + it { expect(post_idv_follow_up_url).to eq(sp_post_idv_follow_up_url) } + end + end end From 078470c5b83f7f001fe7b9d4e86a8a9e1e510011 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <1779930+aduth@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:36:06 -0500 Subject: [PATCH 04/14] Fix Face/Touch recommendation duplicate submission (#11608) * Fall back to after_mfa_setup_path for nil next_setup_path changelog: Bug Fixes, Face/Touch Recommendation, Fix edge case for duplicate submission in recommendation * Use SubmitButtonComponent for Face/Touch recommendation --- ...ebauthn_platform_recommended_controller.rb | 2 +- .../new.html.erb | 7 +++--- ...hn_platform_recommended_controller_spec.rb | 23 +++++++++++++++++-- .../new.html.erb_spec.rb | 5 ++++ 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/controllers/users/webauthn_platform_recommended_controller.rb b/app/controllers/users/webauthn_platform_recommended_controller.rb index 7c8395dfdcc..aa18b9b6eeb 100644 --- a/app/controllers/users/webauthn_platform_recommended_controller.rb +++ b/app/controllers/users/webauthn_platform_recommended_controller.rb @@ -39,7 +39,7 @@ def dismiss_redirect_path if opted_to_add? webauthn_setup_path(platform: true) elsif in_account_creation_flow? - next_setup_path + next_setup_path || after_mfa_setup_path else after_sign_in_path_for(current_user) end diff --git a/app/views/users/webauthn_platform_recommended/new.html.erb b/app/views/users/webauthn_platform_recommended/new.html.erb index a0e357ff886..534df7a338e 100644 --- a/app/views/users/webauthn_platform_recommended/new.html.erb +++ b/app/views/users/webauthn_platform_recommended/new.html.erb @@ -23,18 +23,19 @@
- <%= render ButtonComponent.new( + <%= render SubmitButtonComponent.new( url: webauthn_platform_recommended_url, method: :post, params: { add_method: true }, - big: true, full_width: true, class: 'margin-bottom-2', ).with_content(t('webauthn_platform_recommended.cta')) %> - <%= render ButtonComponent.new( + <%= render SubmitButtonComponent.new( url: webauthn_platform_recommended_url, method: :post, unstyled: true, + big: false, + wide: false, ).with_content(t('webauthn_platform_recommended.skip')) %>
diff --git a/spec/controllers/users/webauthn_platform_recommended_controller_spec.rb b/spec/controllers/users/webauthn_platform_recommended_controller_spec.rb index 853b2b9fe18..13d2c027caf 100644 --- a/spec/controllers/users/webauthn_platform_recommended_controller_spec.rb +++ b/spec/controllers/users/webauthn_platform_recommended_controller_spec.rb @@ -2,8 +2,14 @@ RSpec.describe Users::WebauthnPlatformRecommendedController do let(:user) { create(:user) } + let(:current_sp) { create(:service_provider) } before do + controller.session[:sp] = { + issuer: current_sp.issuer, + acr_values: Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF, + request_url: 'http://example.com', + } stub_sign_in(user) if user end @@ -72,12 +78,25 @@ context 'user is creating account' do before do allow(controller).to receive(:in_account_creation_flow?).and_return(true) - allow(controller).to receive(:next_setup_path).and_return(sign_up_completed_path) + controller.user_session[:mfa_selections] = [] end - it 'redirects user to set up next authenticator' do + it 'redirects user to consent screen' do expect(response).to redirect_to(sign_up_completed_path) end + + context 'mfa selections already completed' do + # Regression: If duplicate submission occurs (e.g. pressing back button), selections is + # already cleared from session, but the user is still in the account creation flow. + + before do + controller.user_session[:mfa_selections] = nil + end + + it 'redirects user to consent screen' do + expect(response).to redirect_to(sign_up_completed_path) + end + end end context 'user opted to add' do diff --git a/spec/views/users/webauthn_platform_recommended/new.html.erb_spec.rb b/spec/views/users/webauthn_platform_recommended/new.html.erb_spec.rb index 951ef168c63..fb217054a22 100644 --- a/spec/views/users/webauthn_platform_recommended/new.html.erb_spec.rb +++ b/spec/views/users/webauthn_platform_recommended/new.html.erb_spec.rb @@ -3,6 +3,11 @@ RSpec.describe 'users/webauthn_platform_recommended/new.html.erb' do subject(:rendered) { render } + it 'renders separate forms with submission for options to add' do + expect(rendered).to have_css('form:has(input[name=add_method]):has([type=submit])') + expect(rendered).to have_css('form:not(:has(input[name=add_method])):has([type=submit])') + end + it 'renders a help link for phishing-resistant including flow path' do @sign_in_flow = :example From 3523bf5f301bdb2dc92abd01aae53a050cc6f862 Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Fri, 6 Dec 2024 10:37:12 -0800 Subject: [PATCH 05/14] Minor refactor of profile handling in EnterPasswordController (#11603) Return a Profile from Idv::Session::create_profile_from_applicant_with_password, then use that directly in EnterPasswordController. [skip changelog] --- app/controllers/idv/enter_password_controller.rb | 7 ++++--- app/services/idv/session.rb | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/idv/enter_password_controller.rb b/app/controllers/idv/enter_password_controller.rb index 64768ea7ec8..c91ab6044a3 100644 --- a/app/controllers/idv/enter_password_controller.rb +++ b/app/controllers/idv/enter_password_controller.rb @@ -127,7 +127,7 @@ def confirm_current_password end def init_profile - idv_session.create_profile_from_applicant_with_password( + profile = idv_session.create_profile_from_applicant_with_password( password, is_enhanced_ipp: resolved_authn_context_result.enhanced_ipp?, proofing_components: ProofingComponents.new( @@ -137,12 +137,13 @@ def init_profile user_session:, ).to_h, ) - if idv_session.verify_by_mail? + + if profile.gpo_verification_pending? current_user.send_email_to_all_addresses(:verify_by_mail_letter_requested) log_letter_enqueued_analytics(resend: false) end - if idv_session.profile.active? + if profile.active? create_user_event(:account_verified) UserAlerts::AlertUserAboutAccountVerified.call( profile: idv_session.profile, diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index 071ce403a80..f7f48f83add 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -105,6 +105,7 @@ def respond_to_missing?(method_sym, include_private) VALID_SESSION_ATTRIBUTES.include?(attr_name_sym) || super end + # @return [Profile] def create_profile_from_applicant_with_password( user_password, is_enhanced_ipp:, proofing_components: ) @@ -141,6 +142,8 @@ def create_profile_from_applicant_with_password( if profile.gpo_verification_pending? create_gpo_entry(profile_maker.pii_attributes, profile) end + + profile end def opt_in_param From 769cd47aca4c31c9457a51ea06543d910dc247eb Mon Sep 17 00:00:00 2001 From: Matt Hinz Date: Fri, 6 Dec 2024 11:53:40 -0800 Subject: [PATCH 06/14] Update erb_lint to 0.7.0 (#11610) * Update erb_lint to 0.7.0 0.5.0 was spitting out warnings related to rubocop deprecations [skip changelog] * Rename .erb-lint.yml to silence warning * We say erb_lint now erb_lint complains if you invoke it with `erblint`. This silences that warning. * Fix a couple more instances of erb-lint --- .erb-lint.yml => .erb_lint.yml | 0 Gemfile | 2 +- Gemfile.lock | 6 +++--- Makefile | 8 ++++---- spec/support/deprecated_classes.rb | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename .erb-lint.yml => .erb_lint.yml (100%) diff --git a/.erb-lint.yml b/.erb_lint.yml similarity index 100% rename from .erb-lint.yml rename to .erb_lint.yml diff --git a/Gemfile b/Gemfile index 1030f0c677c..ee19f1969e6 100644 --- a/Gemfile +++ b/Gemfile @@ -105,7 +105,7 @@ group :development, :test do gem 'brakeman', require: false gem 'bullet', '~> 7.0' gem 'capybara-webmock', git: 'https://github.com/hashrocket/capybara-webmock.git', ref: 'd3f3b7c' - gem 'erb_lint', '~> 0.5.0', require: false + gem 'erb_lint', '~> 0.7.0', require: false gem 'i18n-tasks', '~> 1.0' gem 'knapsack' gem 'listen' diff --git a/Gemfile.lock b/Gemfile.lock index dda8909ff93..037d8d04e17 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -305,12 +305,12 @@ GEM htmlentities (~> 4.3.3) launchy (~> 2.1) mail (~> 2.7) - erb_lint (0.5.0) + erb_lint (0.7.0) activesupport better_html (>= 2.0.1) parser (>= 2.7.1.4) rainbow - rubocop + rubocop (>= 1) smart_properties errbase (0.2.1) erubi (1.13.0) @@ -779,7 +779,7 @@ DEPENDENCIES devise (~> 4.8) dotiw (>= 4.0.1) email_spec - erb_lint (~> 0.5.0) + erb_lint (~> 0.7.0) factory_bot_rails (>= 6.2.0) faker faraday (~> 2) diff --git a/Makefile b/Makefile index f10d272378a..482082e652d 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ check: lint test ## Runs lint tests and spec tests lint: ## Runs all lint tests # Ruby - @echo "--- erb-lint ---" + @echo "--- erb_lint ---" make lint_erb @echo "--- rubocop ---" mkdir -p tmp @@ -112,7 +112,7 @@ audit: ## Checks packages for vulnerabilities yarn audit --groups dependencies; test $$? -le 7 lint_erb: ## Lints ERB files - bundle exec erblint app/views app/components + bundle exec erb_lint app/views app/components lint_yaml: normalize_yaml ## Lints YAML files (! git diff --name-only | grep "^config/.*\.yml") || (echo "Error: Run 'make normalize_yaml' to normalize YAML"; exit 1) @@ -181,8 +181,8 @@ lint_spec_file_name: lintfix: ## Try to automatically fix any Ruby, ERB, JavaScript, YAML, or CSS lint errors @echo "--- rubocop fix ---" bundle exec rubocop -a - @echo "--- erblint fix ---" - bundle exec erblint app/views app/components -a + @echo "--- erb_lint fix ---" + bundle exec erb_lint app/views app/components -a @echo "--- eslint fix ---" yarn lint --fix @echo "--- stylelint fix ---" diff --git a/spec/support/deprecated_classes.rb b/spec/support/deprecated_classes.rb index 333b672b558..16e595e48e1 100644 --- a/spec/support/deprecated_classes.rb +++ b/spec/support/deprecated_classes.rb @@ -1,7 +1,7 @@ class ActionView::Helpers::TagHelper::TagBuilder def self.deprecated_classes @deprecated_classes ||= begin - YAML.safe_load(File.read(File.expand_path('../../../.erb-lint.yml', __FILE__))). + YAML.safe_load(File.read(File.expand_path('../../../.erb_lint.yml', __FILE__))). dig('linters', 'DeprecatedClasses', 'rule_set'). flat_map { |rule| rule['deprecated'] }. map { |regex_str| Regexp.new "^#{regex_str}$" } From 18066d59bff99420a98659580089b23c3457623b Mon Sep 17 00:00:00 2001 From: Shane Chesnutt Date: Fri, 6 Dec 2024 15:01:10 -0500 Subject: [PATCH 07/14] LG-14749 Cancel enrollments on profile encryption error (#11585) changelog: Internal, In-person Proofing, Cancel in-person enrollments when profiles are deactivated due to encryption error. --- .../concerns/ial2_profile_concern.rb | 2 +- app/models/profile.rb | 14 ++ .../concerns/ial2_profile_concern_spec.rb | 213 ++++++++++++++++++ ...get_proofing_results_job_scenarios_spec.rb | 24 +- spec/models/profile_spec.rb | 62 +++++ 5 files changed, 302 insertions(+), 13 deletions(-) create mode 100644 spec/controllers/concerns/ial2_profile_concern_spec.rb diff --git a/app/controllers/concerns/ial2_profile_concern.rb b/app/controllers/concerns/ial2_profile_concern.rb index 44951aefbe0..679660a8d59 100644 --- a/app/controllers/concerns/ial2_profile_concern.rb +++ b/app/controllers/concerns/ial2_profile_concern.rb @@ -23,7 +23,7 @@ def cache_profile_and_handle_errors(raw_password, profile) cacher.save(raw_password, profile) rescue Encryption::EncryptionError => err if profile - profile.deactivate(:encryption_error) + profile.deactivate_due_to_encryption_error analytics.profile_encryption_invalid(error: err.message) end end diff --git a/app/models/profile.rb b/app/models/profile.rb index 2052eb33eaf..e05d3c0a33f 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -191,6 +191,20 @@ def deactivate(reason) update!(active: false, deactivation_reason: reason) end + # Update the profile's deactivation reason to "encryption_error". As a + # side-effect, when the profile has an associated pending in-person + # enrollment it will be updated to have a status of "cancelled". + def deactivate_due_to_encryption_error + update!( + active: false, + deactivation_reason: :encryption_error, + ) + + if in_person_enrollment&.pending? + in_person_enrollment.cancelled! + end + end + def fraud_deactivation_reason? fraud_review_pending? || fraud_rejection? end diff --git a/spec/controllers/concerns/ial2_profile_concern_spec.rb b/spec/controllers/concerns/ial2_profile_concern_spec.rb new file mode 100644 index 00000000000..66fdc0dbbc2 --- /dev/null +++ b/spec/controllers/concerns/ial2_profile_concern_spec.rb @@ -0,0 +1,213 @@ +require 'rails_helper' + +RSpec.describe Ial2ProfileConcern do + let(:test_controller) do + Class.new do + include Ial2ProfileConcern + + attr_accessor :current_user, :user_session, :analytics + + def initialize(current_user:, user_session:, analytics:) + @current_user = current_user + @user_session = user_session + @analytics = analytics + end + end + end + + let(:user) { create(:user) } + let(:user_session) { {} } + let(:analytics) { double(Analytics) } + let(:encryption_error) { Encryption::EncryptionError.new } + let(:cacher) { double(Pii::Cacher) } + let(:password) { 'PraiseTheSun!' } + + describe '#cache_profiles' do + subject { test_controller.new(current_user: user, user_session:, analytics:) } + + context 'when the user has a pending profile' do + let!(:profile) { create(:profile, :in_person_verification_pending, user:) } + + context 'when the profile can be saved to cache' do + before do + allow(cacher).to receive(:save) + allow(Pii::Cacher).to receive(:new).and_return(cacher) + subject.cache_profiles(password) + end + + it 'stores the decrypted profile in cache' do + expect(cacher).to have_received(:save).with(password, profile) + end + end + + context 'when the profile can not be saved to cache' do + before do + allow(cacher).to receive(:save).and_raise(encryption_error) + allow(Pii::Cacher).to receive(:new).and_return(cacher) + allow(analytics).to receive(:profile_encryption_invalid) + subject.cache_profiles(password) + end + + it 'deactivates the profile with reason encryption_error' do + expect(profile.reload).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + ) + end + + it 'logs the profile_encryption_invalid analytic' do + expect(analytics).to have_received(:profile_encryption_invalid).with( + error: encryption_error.message, + ) + end + end + end + + context 'when the user has an active profile' do + let!(:profile) { create(:profile, :active, user:) } + + context 'when the profile can be saved to cache' do + before do + allow(cacher).to receive(:save) + allow(Pii::Cacher).to receive(:new).and_return(cacher) + subject.cache_profiles(password) + end + + it 'stores the decrypted profile in cache' do + expect(cacher).to have_received(:save).with(password, profile) + end + end + + context 'when the profile can not be saved to cache' do + before do + allow(cacher).to receive(:save).and_raise(encryption_error) + allow(Pii::Cacher).to receive(:new).and_return(cacher) + allow(analytics).to receive(:profile_encryption_invalid) + subject.cache_profiles(password) + end + + it 'deactivates the profile with reason encryption_error' do + expect(profile.reload).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + ) + end + + it 'logs the profile_encryption_invalid analytic' do + expect(analytics).to have_received(:profile_encryption_invalid).with( + error: encryption_error.message, + ) + end + end + end + + context 'when the user has both an active profile and pending profile' do + let(:pending_profile) { create(:profile, :in_person_verification_pending, user:) } + let(:active_profile) { create(:profile, :active, user:) } + + context 'when the active profile was activated before the pending profile was created' do + before do + pending_profile.update!(created_at: Time.zone.now) + active_profile.update!(activated_at: 1.day.ago) + end + + context 'when the profiles can be saved to cache' do + before do + allow(cacher).to receive(:save) + allow(Pii::Cacher).to receive(:new).and_return(cacher) + subject.cache_profiles(password) + end + + it 'stores the decrypted pending profile in cache' do + expect(cacher).to have_received(:save).with(password, pending_profile) + end + + it 'stores the decrypted active profile in cache' do + expect(cacher).to have_received(:save).with(password, active_profile) + end + end + + context 'when the profile can not be saved to cache' do + before do + allow(cacher).to receive(:save).and_raise(encryption_error) + allow(Pii::Cacher).to receive(:new).and_return(cacher) + allow(analytics).to receive(:profile_encryption_invalid) + subject.cache_profiles(password) + end + + it 'deactivates the pending profile with reason encryption_error' do + expect(pending_profile.reload).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + ) + end + + it 'deactivates the active profile with reason encryption_error' do + expect(active_profile.reload).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + ) + end + + it 'logs the profile_encryption_invalid analytic' do + expect(analytics).to have_received(:profile_encryption_invalid).with( + error: encryption_error.message, + ).twice + end + end + end + + context 'when the active profile was activated after the pending profile was created' do + before do + pending_profile.update!(created_at: 1.day.ago) + active_profile.update!(activated_at: Time.zone.now) + end + + context 'when the profiles can be saved to cache' do + before do + allow(cacher).to receive(:save) + allow(Pii::Cacher).to receive(:new).and_return(cacher) + subject.cache_profiles(password) + end + + it 'does not store the decrypted pending profile in cache' do + expect(cacher).not_to have_received(:save).with(password, pending_profile) + end + + it 'stores the decrypted active profile in cache' do + expect(cacher).to have_received(:save).with(password, active_profile) + end + end + + context 'when the profile can not be saved to cache' do + before do + allow(cacher).to receive(:save).and_raise(encryption_error) + allow(Pii::Cacher).to receive(:new).and_return(cacher) + allow(analytics).to receive(:profile_encryption_invalid) + subject.cache_profiles(password) + end + + it 'does not deactivate the pending profile with reason encryption_error' do + expect(pending_profile.reload).to have_attributes( + active: false, + deactivation_reason: nil, + ) + end + + it 'deactivates the active profile with reason encryption_error' do + expect(active_profile.reload).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + ) + end + + it 'logs the profile_encryption_invalid analytic' do + expect(analytics).to have_received(:profile_encryption_invalid).with( + error: encryption_error.message, + ).once + end + end + end + end + end +end diff --git a/spec/features/idv/get_proofing_results_job_scenarios_spec.rb b/spec/features/idv/get_proofing_results_job_scenarios_spec.rb index 08be64fae88..f28bc7fe37c 100644 --- a/spec/features/idv/get_proofing_results_job_scenarios_spec.rb +++ b/spec/features/idv/get_proofing_results_job_scenarios_spec.rb @@ -51,9 +51,9 @@ # Then the user is taken to the /verify/welcome page expect(current_path).to eq(idv_welcome_path) - # And the user has an InPersonEnrollment with status "pending" + # And the user has an InPersonEnrollment with status "cancelled" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'pending', + status: 'cancelled', ) # And the user has a Profile that is deactivated with reason "encryption_error" expect(@user.in_person_enrollments.first.profile).to have_attributes( @@ -78,7 +78,7 @@ expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, deactivation_reason: 'encryption_error', - in_person_verification_pending_at: nil, + in_person_verification_pending_at: be_kind_of(Time), ) # When the user logs in @@ -94,7 +94,7 @@ expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, deactivation_reason: 'encryption_error', - in_person_verification_pending_at: nil, + in_person_verification_pending_at: be_kind_of(Time), ) end @@ -130,9 +130,9 @@ # Then the user is taken to the /verify/welcome page expect(current_path).to eq(idv_welcome_path) - # And the user has an InPersonEnrollment with status "pending" + # And the user has an InPersonEnrollment with status "cancelled" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'pending', + status: 'cancelled', ) # And the user has a Profile that is deactivated with reason "encryption_error" expect(@user.in_person_enrollments.first.profile).to have_attributes( @@ -157,7 +157,7 @@ expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, deactivation_reason: 'encryption_error', - in_person_verification_pending_at: nil, + in_person_verification_pending_at: be_kind_of(Time), ) # When the user logs in @@ -173,7 +173,7 @@ expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, deactivation_reason: 'encryption_error', - in_person_verification_pending_at: nil, + in_person_verification_pending_at: be_kind_of(Time), ) end end @@ -537,9 +537,9 @@ # Then the user is taken to the /verify/welcome page expect(current_path).to eq(idv_welcome_path) - # And the user has an InPersonEnrollment with status "pending" + # And the user has an InPersonEnrollment with status "cancelled" expect(@user.in_person_enrollments.first).to have_attributes( - status: 'pending', + status: 'cancelled', ) # And the user has a Profile that is deactivated with reason "encryption_error" and # pending in person verification and fraud review @@ -568,7 +568,7 @@ expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, deactivation_reason: 'encryption_error', - in_person_verification_pending_at: nil, + in_person_verification_pending_at: be_kind_of(Time), fraud_pending_reason: 'threatmetrix_review', fraud_review_pending_at: be_kind_of(Time), ) @@ -587,7 +587,7 @@ expect(@user.in_person_enrollments.first.profile).to have_attributes( active: false, deactivation_reason: 'encryption_error', - in_person_verification_pending_at: nil, + in_person_verification_pending_at: be_kind_of(Time), fraud_pending_reason: 'threatmetrix_review', fraud_review_pending_at: be_kind_of(Time), ) diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb index 57f9631c173..336c6eee4d4 100644 --- a/spec/models/profile_spec.rb +++ b/spec/models/profile_spec.rb @@ -609,6 +609,68 @@ end end + describe '#deactivate_due_to_encryption_error' do + context 'when the profile has a "pending" in_person_enrollment' do + subject { create(:profile, :in_person_verification_pending, user: user) } + let!(:enrollment) do + create(:in_person_enrollment, user: user, profile: subject, status: :pending) + end + + before do + subject.deactivate_due_to_encryption_error + end + + it 'deactivates with reason encryption_error' do + expect(subject).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + in_person_verification_pending_at: be_kind_of(Time), + ) + end + + it 'cancels the associated pending in_person_enrollment' do + expect(subject.in_person_enrollment.status).to eq('cancelled') + end + end + + context 'when the profile has a "passed" in_person_enrollment' do + subject { create(:profile, :active, user: user) } + let!(:enrollment) do + create(:in_person_enrollment, user: user, profile: subject, status: :passed) + end + + before do + subject.deactivate_due_to_encryption_error + end + + it 'deactivates with reason encryption_error' do + expect(subject).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + ) + end + + it 'does not cancel the associated pending in_person_enrollment' do + expect(subject.in_person_enrollment.status).to eq('passed') + end + end + + context 'when the profile has no in_person_enrollment' do + subject { create(:profile, :active, user: user) } + + before do + subject.deactivate_due_to_encryption_error + end + + it 'deactivates with reason encryption_error' do + expect(subject).to have_attributes( + active: false, + deactivation_reason: 'encryption_error', + ) + end + end + end + describe '#remove_gpo_deactivation_reason' do it 'removes the gpo_verification_pending_at deactivation reason' do profile = create(:profile, :verify_by_mail_pending) From 5e8b9e825f9cbd52b3556c405fa882bf56644c2f Mon Sep 17 00:00:00 2001 From: Matt Wagner Date: Fri, 6 Dec 2024 15:37:17 -0500 Subject: [PATCH 08/14] LG-14972 | Fix XML fixture; implement match_xml matcher (#11599) changelog: Internal, RSpec Matchers, Adds match_xml matcher and cleans up gross fixture --- .../aamva/requests/verification_request.xml | 3 +- spec/rails_helper.rb | 1 + .../request/verification_request_spec.rb | 2 +- spec/support/diff_helper.rb | 14 ++ spec/support/fake_analytics_spec.rb | 13 -- spec/support/xml_helper.rb | 9 ++ spec/support/xml_matcher.rb | 18 +++ spec/support/xml_matcher_spec.rb | 146 ++++++++++++++++++ 8 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 spec/support/diff_helper.rb create mode 100644 spec/support/xml_matcher.rb create mode 100644 spec/support/xml_matcher_spec.rb diff --git a/spec/fixtures/proofing/aamva/requests/verification_request.xml b/spec/fixtures/proofing/aamva/requests/verification_request.xml index c42882043d4..ef7dfd1243f 100644 --- a/spec/fixtures/proofing/aamva/requests/verification_request.xml +++ b/spec/fixtures/proofing/aamva/requests/verification_request.xml @@ -31,7 +31,8 @@ VA 20176 - 1 + 1 + diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 95b8bcb97e6..4dc85032e9f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -44,6 +44,7 @@ config.include AnalyticsHelper config.include AwsCloudwatchHelper config.include AwsKmsClientHelper + config.include DiffHelper config.include KeyRotationHelper config.include OtpHelper config.include XmlHelper diff --git a/spec/services/proofing/aamva/request/verification_request_spec.rb b/spec/services/proofing/aamva/request/verification_request_spec.rb index 187b766fd01..79b52ef2716 100644 --- a/spec/services/proofing/aamva/request/verification_request_spec.rb +++ b/spec/services/proofing/aamva/request/verification_request_spec.rb @@ -33,7 +33,7 @@ describe '#body' do it 'should be a request body' do - expect(subject.body + "\n").to eq(AamvaFixtures.verification_request) + expect(subject.body).to match_xml(AamvaFixtures.verification_request) end it 'should escape XML in applicant data' do diff --git a/spec/support/diff_helper.rb b/spec/support/diff_helper.rb new file mode 100644 index 00000000000..be56abc0a99 --- /dev/null +++ b/spec/support/diff_helper.rb @@ -0,0 +1,14 @@ +module DiffHelper + def assert_error_messages_equal(err, expected) + actual = normalize_error_message(err.message) + expected = normalize_error_message(expected) + expect(actual).to eql(expected) + end + + def normalize_error_message(message) + message. + gsub(/\x1b\[[0-9;]*m/, ''). # Strip ANSI control characters used for color + gsub(/:0x[0-9a-f]{16}/, ':'). + strip + end +end diff --git a/spec/support/fake_analytics_spec.rb b/spec/support/fake_analytics_spec.rb index ef2dda16d5f..e1b9291ea28 100644 --- a/spec/support/fake_analytics_spec.rb +++ b/spec/support/fake_analytics_spec.rb @@ -647,17 +647,4 @@ ) end end - - def assert_error_messages_equal(err, expected) - actual = normalize_error_message(err.message) - expected = normalize_error_message(expected) - expect(actual).to eql(expected) - end - - def normalize_error_message(message) - message. - gsub(/\x1b\[[0-9;]*m/, ''). # Strip ANSI control characters used for color - gsub(/:0x[0-9a-f]{16}/, ':'). - strip - end end diff --git a/spec/support/xml_helper.rb b/spec/support/xml_helper.rb index eaa46261f3e..25107cbd326 100644 --- a/spec/support/xml_helper.rb +++ b/spec/support/xml_helper.rb @@ -21,4 +21,13 @@ def delete_xml_at_xpath(xml, xpath) element.parent.delete(element) document.to_s end + + def pretty_xml_from_string(document) + output = String.new(encoding: 'UTF-8') + doc = REXML::Document.new(document) + formatter = REXML::Formatters::Pretty.new + formatter.compact = true + formatter.write(doc, output) + output.to_s + end end diff --git a/spec/support/xml_matcher.rb b/spec/support/xml_matcher.rb new file mode 100644 index 00000000000..55b80342457 --- /dev/null +++ b/spec/support/xml_matcher.rb @@ -0,0 +1,18 @@ +RSpec::Matchers.define :match_xml do |comparison| + diffable + + # REXML::Documents do not implement comparison; they are never ==. + # This matcher considers the documents the same if their string outputs + # are equal after both going through the REXML::Formatters::Pretty. + match do |document| + # We have to override these for the diff to use these, rather than input strings + @actual = XmlHelper.pretty_xml_from_string(document) + @expected = XmlHelper.pretty_xml_from_string(comparison) + + expect(@actual).to eq(@expected) + end + + failure_message do + 'Expected XML documents to be the same, but they differed:' + end +end diff --git a/spec/support/xml_matcher_spec.rb b/spec/support/xml_matcher_spec.rb new file mode 100644 index 00000000000..fda2b643764 --- /dev/null +++ b/spec/support/xml_matcher_spec.rb @@ -0,0 +1,146 @@ +require 'rails_helper' + +RSpec.describe 'match_xml custom matcher' do + let(:weirdly_formatted_carrot_xml) do + <<~XML + + CarrotOrange (usually) + + + + + XML + end + + let(:normally_formatted_carrot_xml) do + <<~XML + + Carrot + Orange (usually) + + XML + end + + let(:carrot_xml_in_a_different_order) do + <<~XML + + Orange (usually) + Carrot + + XML + end + + let(:all_caps_carrot_xml) do + <<~XML + + Carrot + Orange (usually) + + XML + end + + let(:tubers_xml) do + <<~XML + + + + + XML + end + + describe 'match_xml' do + context 'when the documents are identical' do + it 'matches' do + expect(weirdly_formatted_carrot_xml).to match_xml(weirdly_formatted_carrot_xml) + end + end + + context 'when the documents are formatted differently but have the same content' do + it 'matches' do + expect(weirdly_formatted_carrot_xml).to match_xml(normally_formatted_carrot_xml) + end + end + + context 'when there is a case difference between tags' do + let(:code_under_test) do + -> { expect(normally_formatted_carrot_xml).to match_xml(all_caps_carrot_xml) } + end + + it 'does not match' do + expect(normally_formatted_carrot_xml).to_not match_xml(all_caps_carrot_xml) + end + + it 'generates a useful diff' do + expect(&code_under_test).to raise_error(RSpec::Expectations::ExpectationNotMetError) do |e| + assert_error_messages_equal(e, <<~ERROR) + Expected XML documents to be the same, but they differed: + Diff: + + @@ -1,5 +1,5 @@ + - + + + Carrot + Orange (usually) + - + + + ERROR + end + end + end + + context 'when the data is the same but ordered differently' do + let(:code_under_test) do + -> { expect(normally_formatted_carrot_xml).to match_xml(carrot_xml_in_a_different_order) } + end + + # This illustrates current behavior, but is not necessarily a mandate. + it 'does not match' do + expect(normally_formatted_carrot_xml).not_to match_xml(carrot_xml_in_a_different_order) + end + + it 'generates a useful diff' do + expect(&code_under_test).to raise_error(RSpec::Expectations::ExpectationNotMetError) do |e| + assert_error_messages_equal(e, <<~ERROR) + Expected XML documents to be the same, but they differed: + Diff: + + @@ -1,5 +1,5 @@ + + - Orange (usually) + Carrot + + Orange (usually) + + ERROR + end + end + end + + context 'when the documents are wholly different' do + let(:code_under_test) do + -> { expect(normally_formatted_carrot_xml).to match_xml(tubers_xml) } + end + + it 'does not match' do + expect(normally_formatted_carrot_xml).not_to match_xml(tubers_xml) + end + + it 'generates a useful diff' do + expect(&code_under_test).to raise_error(RSpec::Expectations::ExpectationNotMetError) do |e| + assert_error_messages_equal(e, <<~ERROR) + Expected XML documents to be the same, but they differed: + Diff: + @@ -1,5 +1,5 @@ + - + - + - + - + + + + Carrot + + Orange (usually) + + + ERROR + end + end + end + end +end From 31ed8e2c45f273da5039dede5fa4423ccf67b448 Mon Sep 17 00:00:00 2001 From: "Davida (she/they)" Date: Fri, 6 Dec 2024 16:49:45 -0500 Subject: [PATCH 09/14] changelog: User-Facing Improvements, Integration Experience, Adding a better error for a testing scenario (#11609) --- app/services/saml_request_validator.rb | 16 ++++++++-- config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fr.yml | 1 + config/locales/zh.yml | 1 + spec/controllers/saml_idp_controller_spec.rb | 33 ++++++++++++++++++++ spec/services/saml_request_validator_spec.rb | 23 ++++++++++++++ 7 files changed, 74 insertions(+), 2 deletions(-) diff --git a/app/services/saml_request_validator.rb b/app/services/saml_request_validator.rb index d055f00c766..3364854d94e 100644 --- a/app/services/saml_request_validator.rb +++ b/app/services/saml_request_validator.rb @@ -3,8 +3,9 @@ class SamlRequestValidator include ActiveModel::Model - validate :cert_exists + validate :request_cert_exists validate :authorized_service_provider + validate :registered_cert_exists validate :authorized_authn_context validate :parsable_vtr validate :authorized_email_nameid_format @@ -72,7 +73,18 @@ def parsable_vtr end end - def cert_exists + def registered_cert_exists + # if there is no service provider, this error has already been added + return if service_provider.blank? + return if service_provider.certs.present? + + errors.add( + :service_provider, :no_cert_registered, + type: :no_cert_registered + ) + end + + def request_cert_exists if @blank_cert errors.add(:service_provider, :blank_cert_element_req, type: :blank_cert_element_req) end diff --git a/config/locales/en.yml b/config/locales/en.yml index 7593fbd2ff2..2080c957568 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -765,6 +765,7 @@ errors.messages.invalid_recaptcha_token: We’re sorry, but your computer or net errors.messages.invalid_sms_number: The phone number entered doesn’t support text messaging. Try the Phone call option. errors.messages.invalid_voice_number: Invalid phone number. Check that you’ve entered the correct country code or area code. errors.messages.missing_field: Please fill in this field. +errors.messages.no_cert_registered: Your service provider does not have a certificate registered. errors.messages.no_pending_profile: No profile is waiting for verification errors.messages.not_a_number: is not a number errors.messages.otp_format: Enter your entire one-time code without spaces or special characters diff --git a/config/locales/es.yml b/config/locales/es.yml index baffc5ec289..054aee76076 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -776,6 +776,7 @@ errors.messages.invalid_recaptcha_token: Lo sentimos, pero es posible que tu com errors.messages.invalid_sms_number: El número de teléfono ingresado no admite mensajes de texto. Intente la opción de llamada telefónica. errors.messages.invalid_voice_number: Número de teléfono no válido. Verifique haber ingresado el código de país o de área correcto. errors.messages.missing_field: Llene este campo. +errors.messages.no_cert_registered: No podemos detectar un certificado en su solicitud. errors.messages.no_pending_profile: No hay ningún perfil en espera de verificación errors.messages.not_a_number: no es un número errors.messages.otp_format: Ingrese su código de un solo uso completo, sin espacios ni caracteres especiales. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index dc2ae99a5ed..3c1c53bd8a2 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -765,6 +765,7 @@ errors.messages.invalid_recaptcha_token: Désolé, il est possible que votre ord errors.messages.invalid_sms_number: Le numéro de téléphone saisi ne prend pas en charge les messages texte. Veuillez essayer l’option d’appel téléphonique. errors.messages.invalid_voice_number: Numéro de téléphone non valide. Vérifiez que vous avez entré le bon indicatif international ou régional. errors.messages.missing_field: Veuillez remplir ce champ. +errors.messages.no_cert_registered: Nous ne pouvons pas détecter un certificat sur votre demande. errors.messages.no_pending_profile: Aucun profil en attente de vérification errors.messages.not_a_number: n’est pas un chiffre errors.messages.otp_format: Saisissez l’intégralité de votre code à usage unique sans espaces ni caractères spéciaux diff --git a/config/locales/zh.yml b/config/locales/zh.yml index ea75d8f839a..d6068d37407 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -776,6 +776,7 @@ errors.messages.invalid_recaptcha_token: 你必须完成预防滥发邮件测验 errors.messages.invalid_sms_number: 输入的电话号码不支持短信。尝试接听电话选项。 errors.messages.invalid_voice_number: 电话号码有误。检查一下你是否输入了正确的国家代码或区域代码。 errors.messages.missing_field: 请填写这一字段。 +errors.messages.no_cert_registered: 我们在你的请求中探查不到证书。 errors.messages.no_pending_profile: 没有等待验证的用户资料 errors.messages.not_a_number: 不是数字 errors.messages.otp_format: 输入你完整的一次性代码(没有空白或特殊字符) diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index c2f6cacea8d..b5ad4de18da 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -1334,6 +1334,39 @@ def name_id_version(format_urn) end end + context 'when service provider has no certs' do + let(:service_provider) do + create( + :service_provider, + certs: [], + active: true, + ) + end + + let(:settings) do + saml_settings.tap do |settings| + settings.issuer = service_provider.issuer + end + end + + it 'returns an error page' do + user = create(:user, :fully_registered) + stub_analytics + + generate_saml_response(user, settings) + + expect(response.body).to include(t('errors.messages.no_cert_registered')) + expect(@analytics).to have_logged_event( + 'SAML Auth', + hash_including( + success: false, + errors: { service_provider: [t('errors.messages.no_cert_registered')] }, + error_details: { service_provider: { no_cert_registered: true } }, + ), + ) + end + end + context 'service provider has multiple certs' do let(:service_provider) do create( diff --git a/spec/services/saml_request_validator_spec.rb b/spec/services/saml_request_validator_spec.rb index bf530ff771d..c6c3e512a7a 100644 --- a/spec/services/saml_request_validator_spec.rb +++ b/spec/services/saml_request_validator_spec.rb @@ -49,6 +49,29 @@ end end + context 'when the sp has no certs registered' do + before { sp.update!(certs: nil) } + let(:errors) do + { + service_provider: [t('errors.messages.no_cert_registered')], + } + end + let(:error_details) do + { + service_provider: { + no_cert_registered: true, + }, + } + end + + it 'returns an error' do + expect(response.to_h).to include( + errors:, + error_details:, + ) + end + end + context 'ialmax authncontext and ialmax provider' do let(:authn_context) { [Saml::Idp::Constants::IALMAX_AUTHN_CONTEXT_CLASSREF] } From 5b99b74324d4ce83d7a86fb7a19e710b97aac060 Mon Sep 17 00:00:00 2001 From: Amir Reavis-Bey Date: Fri, 6 Dec 2024 17:03:57 -0500 Subject: [PATCH 10/14] LG-15150: docv test mode (#11546) * rebase - merge conflicts resolved * [skip changelog] * rebase - merge conflicts resolved * defualt docv_transaction_token_override to nil in doc reesult request * test docvTransaction override * add docv token override tests to hybrid flow * rebase - merge conflicts resolved * undo the destroy of all users and email addresses * rebase - merge conflicts resolved * rebase - merge conflicts resolved * rebase - merge conflicts resolved * remove public/browserconfig.xml * add needed end empty line * fix signature post rebase * happy linting * add test when user does/not complete docv * refactor to use guards * combine specs for invalid doc token during docv test mode * refacgtor mobile socure test to watit for docv token --- .../concerns/idv/document_capture_concern.rb | 14 +++ .../socure/document_capture_controller.rb | 1 + .../idv/socure/document_capture_controller.rb | 1 + app/jobs/socure_docv_results_job.rb | 6 +- .../socure/requests/docv_result_request.rb | 19 +++- config/application.yml.default | 2 + lib/identity_config.rb | 2 + .../document_capture_controller_spec.rb | 53 +++++++++++ .../document_capture_controller_spec.rb | 52 ++++++++++ .../doc_auth/socure_document_capture_spec.rb | 76 ++++++++++++--- .../hybrid_socure_mobile_spec.rb | 95 ++++++++++++++++++- .../features/document_capture_step_helper.rb | 16 +++- 12 files changed, 313 insertions(+), 24 deletions(-) diff --git a/app/controllers/concerns/idv/document_capture_concern.rb b/app/controllers/concerns/idv/document_capture_concern.rb index 790f0c64907..32835a320a8 100644 --- a/app/controllers/concerns/idv/document_capture_concern.rb +++ b/app/controllers/concerns/idv/document_capture_concern.rb @@ -81,6 +81,20 @@ def redirect_to_correct_vendor(vendor, in_hybrid_mobile) redirect_to correct_path end + def fetch_test_verification_data + return unless IdentityConfig.store.socure_docv_verification_data_test_mode + + docv_transaction_token_override = params.permit(:docv_token)[:docv_token] + return unless IdentityConfig.store.socure_docv_verification_data_test_mode_tokens. + include?(docv_transaction_token_override) + + SocureDocvResultsJob.perform_now( + document_capture_session_uuid:, + docv_transaction_token_override:, + async: true, + ) + end + private def track_document_issuing_state(user, state) diff --git a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb index 44fccfa0182..b3d36f455ad 100644 --- a/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb +++ b/app/controllers/idv/hybrid_mobile/socure/document_capture_controller.rb @@ -14,6 +14,7 @@ class DocumentCaptureController < ApplicationController before_action :check_valid_document_capture_session, except: [:update] before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, true) }, only: :show + before_action :fetch_test_verification_data, only: [:update] def show Funnel::DocAuth::RegisterStep.new(document_capture_user.id, sp_session[:issuer]). diff --git a/app/controllers/idv/socure/document_capture_controller.rb b/app/controllers/idv/socure/document_capture_controller.rb index 3f29ad659e1..7636e9a8275 100644 --- a/app/controllers/idv/socure/document_capture_controller.rb +++ b/app/controllers/idv/socure/document_capture_controller.rb @@ -14,6 +14,7 @@ class DocumentCaptureController < ApplicationController before_action :confirm_step_allowed before_action -> { redirect_to_correct_vendor(Idp::Constants::Vendors::SOCURE, false) }, only: :show + before_action :fetch_test_verification_data, only: [:update] # reconsider and maybe remove these when implementing the real # update handler diff --git a/app/jobs/socure_docv_results_job.rb b/app/jobs/socure_docv_results_job.rb index 25729078575..b4142a3b2b8 100644 --- a/app/jobs/socure_docv_results_job.rb +++ b/app/jobs/socure_docv_results_job.rb @@ -3,12 +3,13 @@ class SocureDocvResultsJob < ApplicationJob queue_as :high_socure_docv - attr_reader :document_capture_session_uuid, :async + attr_reader :document_capture_session_uuid, :async, :docv_transaction_token_override # @param [String] document_capture_session_uuid - def perform(document_capture_session_uuid:, async: true) + def perform(document_capture_session_uuid:, async: true, docv_transaction_token_override: nil) @document_capture_session_uuid = document_capture_session_uuid @async = async + @docv_transaction_token_override = docv_transaction_token_override raise "DocumentCaptureSession not found: #{document_capture_session_uuid}" unless document_capture_session @@ -51,6 +52,7 @@ def log_verification_request(docv_result_response:, vendor_request_time_in_ms:) def socure_document_verification_result DocAuth::Socure::Requests::DocvResultRequest.new( document_capture_session_uuid:, + docv_transaction_token_override:, ).fetch end diff --git a/app/services/doc_auth/socure/requests/docv_result_request.rb b/app/services/doc_auth/socure/requests/docv_result_request.rb index 7f4bc26d116..8a3662343a6 100644 --- a/app/services/doc_auth/socure/requests/docv_result_request.rb +++ b/app/services/doc_auth/socure/requests/docv_result_request.rb @@ -6,8 +6,13 @@ module Requests class DocvResultRequest < DocAuth::Socure::Request attr_reader :document_capture_session_uuid, :biometric_comparison_required - def initialize(document_capture_session_uuid:, biometric_comparison_required: false) + def initialize( + document_capture_session_uuid:, + docv_transaction_token_override: nil, + biometric_comparison_required: false + ) @document_capture_session_uuid = document_capture_session_uuid + @docv_transaction_token_override = docv_transaction_token_override @biometric_comparison_required = biometric_comparison_required end @@ -16,7 +21,7 @@ def initialize(document_capture_session_uuid:, biometric_comparison_required: fa def body { modules: ['documentverification'], - docvTransactionToken: document_capture_session.socure_docv_transaction_token, + docvTransactionToken: docv_transaction_token, }.to_json end @@ -60,6 +65,16 @@ def endpoint def metric_name 'socure_id_plus_document_verification' end + + def docv_transaction_token + if IdentityConfig.store.socure_docv_verification_data_test_mode && + IdentityConfig.store.socure_docv_verification_data_test_mode_tokens. + include?(@docv_transaction_token_override) + return @docv_transaction_token_override + end + + document_capture_session.socure_docv_transaction_token + end end end end diff --git a/config/application.yml.default b/config/application.yml.default index 86e01893ec2..549a443c771 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -385,6 +385,8 @@ sign_in_user_id_per_ip_max_attempts: 50 skip_encryption_allowed_list: '["urn:gov:gsa:SAML:2.0.profiles:sp:sso:dev", "urn:gov:gsa:SAML:2.0.profiles:sp:sso:int"]' socure_docv_document_request_endpoint: '' socure_docv_enabled: false +socure_docv_verification_data_test_mode: false +socure_docv_verification_data_test_mode_tokens: '[]' socure_docv_webhook_secret_key: '' socure_docv_webhook_secret_key_queue: '[]' socure_idplus_api_key: '' diff --git a/lib/identity_config.rb b/lib/identity_config.rb index b66644e7792..cd90324d7ed 100644 --- a/lib/identity_config.rb +++ b/lib/identity_config.rb @@ -421,6 +421,8 @@ def self.store config.add(:recommend_webauthn_platform_for_sms_ab_test_authentication_percent, type: :integer) config.add(:socure_docv_document_request_endpoint, type: :string) config.add(:socure_docv_enabled, type: :boolean) + config.add(:socure_docv_verification_data_test_mode, type: :boolean) + config.add(:socure_docv_verification_data_test_mode_tokens, type: :json) config.add(:socure_docv_webhook_secret_key_queue, type: :json) config.add(:socure_docv_webhook_secret_key, type: :string) config.add(:socure_idplus_api_key, type: :string) diff --git a/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb b/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb index 636e81c430b..faab1251e0d 100644 --- a/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb +++ b/spec/controllers/idv/hybrid_mobile/socure/document_capture_controller_spec.rb @@ -18,6 +18,8 @@ end let(:document_capture_session_uuid) { document_capture_session&.uuid } + let(:socure_docv_verification_data_test_mode) { false } + before do allow(IdentityConfig.store).to receive(:socure_docv_enabled). and_return(socure_docv_enabled) @@ -33,6 +35,14 @@ session[:doc_capture_user_id] = user&.id session[:document_capture_session_uuid] = document_capture_session_uuid + allow(IdentityConfig.store). + to receive(:socure_docv_verification_data_test_mode). + and_return(socure_docv_verification_data_test_mode) + + unless IdentityConfig.store.socure_docv_verification_data_test_mode + expect(IdentityConfig.store).not_to receive(:socure_docv_verification_data_test_mode_tokens) + end + stub_analytics end @@ -359,5 +369,48 @@ end end end + + context 'when socure_docv_verification_data_test_mode is enabled' do + let(:test_token) { '12345' } + let(:socure_docv_verification_data_test_mode) { true } + + before do + ActiveJob::Base.queue_adapter = :test + allow(IdentityConfig.store). + to receive(:socure_docv_verification_data_test_mode_tokens). + and_return([test_token]) + + stub_request( + :post, + "#{IdentityConfig.store.socure_idplus_base_url}/api/3.0/EmailAuthScore", + ). + with(body: { modules: ['documentverification'], docvTransactionToken: test_token }. + to_json). + to_return( + headers: { + 'Content-Type' => 'application/json', + }, + body: SocureDocvFixtures.pass_json, + ) + end + + context 'when a token is provided from the allow list' do + it 'performs SocureDocvResultsJob' do + expect { get(:update, params: { docv_token: test_token }) }. + not_to have_enqueued_job(SocureDocvResultsJob) # is synchronous + + expect(document_capture_session.reload.load_result).not_to be_nil + end + end + + context 'when a token is provided not on the allow list' do + it 'performs SocureDocvResultsJob' do + expect { get(:update, params: { docv_token: 'rando-token' }) }. + not_to have_enqueued_job(SocureDocvResultsJob) + + expect(document_capture_session.reload.load_result).to be_nil + end + end + end end end diff --git a/spec/controllers/idv/socure/document_capture_controller_spec.rb b/spec/controllers/idv/socure/document_capture_controller_spec.rb index 09b00461415..132d570b24f 100644 --- a/spec/controllers/idv/socure/document_capture_controller_spec.rb +++ b/spec/controllers/idv/socure/document_capture_controller_spec.rb @@ -27,6 +27,8 @@ ) end + let(:socure_docv_verification_data_test_mode) { false } + before do allow(IdentityConfig.store).to receive(:socure_docv_enabled). and_return(socure_docv_enabled) @@ -44,6 +46,13 @@ allow(subject).to receive(:user_session).and_return(user_session) subject.idv_session.document_capture_session_uuid = document_capture_session.uuid + allow(IdentityConfig.store). + to receive(:socure_docv_verification_data_test_mode). + and_return(socure_docv_verification_data_test_mode) + + unless IdentityConfig.store.socure_docv_verification_data_test_mode + expect(IdentityConfig.store).not_to receive(:socure_docv_verification_data_test_mode_tokens) + end stub_analytics end @@ -341,6 +350,49 @@ expect(response.body).to eq('Technical difficulties!!!') end end + + context 'when socure_docv_verification_data_test_mode is enabled' do + let(:test_token) { '12345' } + let(:socure_docv_verification_data_test_mode) { true } + + before do + ActiveJob::Base.queue_adapter = :test + allow(IdentityConfig.store). + to receive(:socure_docv_verification_data_test_mode_tokens). + and_return([test_token]) + + stub_request( + :post, + "#{IdentityConfig.store.socure_idplus_base_url}/api/3.0/EmailAuthScore", + ). + with(body: { modules: ['documentverification'], docvTransactionToken: test_token }. + to_json). + to_return( + headers: { + 'Content-Type' => 'application/json', + }, + body: SocureDocvFixtures.pass_json, + ) + end + + context 'when a token is provided from the allow list' do + it 'performs SocureDocvResultsJob' do + expect { get(:update, params: { docv_token: test_token }) }. + not_to have_enqueued_job(SocureDocvResultsJob) # is synchronous + + expect(document_capture_session.reload.load_result).not_to be_nil + end + end + + context 'when a token is provided not on the allow list' do + it 'performs SocureDocvResultsJob' do + expect { get(:update, params: { docv_token: 'rando-token' }) }. + not_to have_enqueued_job(SocureDocvResultsJob) + + expect(document_capture_session.reload.load_result).to be_nil + end + end + end end context 'when socure is disabled' do diff --git a/spec/features/idv/doc_auth/socure_document_capture_spec.rb b/spec/features/idv/doc_auth/socure_document_capture_spec.rb index b645ea89562..0c3a39c5a03 100644 --- a/spec/features/idv/doc_auth/socure_document_capture_spec.rb +++ b/spec/features/idv/doc_auth/socure_document_capture_spec.rb @@ -11,6 +11,7 @@ let(:socure_docv_webhook_secret_key) { 'socure_docv_webhook_secret_key' } let(:fake_socure_docv_document_request_endpoint) { 'https://fake-socure.test/document-request' } let(:fake_socure_document_capture_app_url) { 'https://verify.fake-socure.test/something' } + let(:socure_docv_verification_data_test_mode) { false } before(:each) do allow(IdentityConfig.store).to receive(:socure_docv_enabled).and_return(true) @@ -24,23 +25,19 @@ allow(IdentityConfig.store).to receive(:ruby_workers_idv_enabled).and_return(false) allow_any_instance_of(ApplicationController).to receive(:analytics).and_return(fake_analytics) @docv_transaction_token = stub_docv_document_request + allow(IdentityConfig.store).to receive(:socure_docv_verification_data_test_mode). + and_return(socure_docv_verification_data_test_mode) end - before(:all) do - @user = user_with_2fa - end - - after(:all) { @user.destroy } - context 'happy path' do before do - stub_docv_verification_data_pass + @pass_stub = stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token) end context 'standard desktop flow' do before do visit_idp_from_oidc_sp_with_ial2 - sign_in_and_2fa_user(@user) + @user = sign_in_and_2fa_user complete_doc_auth_steps_before_document_capture_step click_idv_continue end @@ -122,6 +119,7 @@ docv_transaction_token: @docv_transaction_token, ) + visit idv_socure_document_capture_update_path expect(DocAuthLog.find_by(user_id: @user.id).state).to be_nil end @@ -131,15 +129,68 @@ docv_transaction_token: @docv_transaction_token, ) + visit idv_socure_document_capture_update_path expect(DocAuthLog.find_by(user_id: @user.id).state).not_to be_nil end + + context 'when socure_docv_verification_data_test_mode is enabled' do + let(:test_token) { 'valid-test-token' } + let(:socure_docv_verification_data_test_mode) { true } + before do + allow(IdentityConfig.store).to receive(:socure_docv_verification_data_test_mode_tokens). + and_return([test_token]) + DocAuth::Mock::DocAuthMockClient.reset! + end + + context 'when a valid test token is used' do + it 'fetches verificationdata using override docvToken in request', + allow_browser_log: true do + remove_request_stub(@pass_stub) + stub_docv_verification_data_pass(docv_transaction_token: test_token) + + visit idv_socure_document_capture_update_path(docv_token: test_token) + expect(page).to have_current_path(idv_ssn_url) + + expect(DocAuthLog.find_by(user_id: @user.id).state).to eq('NY') + + fill_out_ssn_form_ok + click_idv_continue + complete_verify_step + expect(page).to have_current_path(idv_phone_url) + end + end + + context 'when an invalid test token is used' do + let(:invalid_token) { 'invalid-token' } + it 'waits to fetch verificationdata using docv capture session token' do + visit idv_socure_document_capture_update_path(docv_token: invalid_token) + + expect(page).to have_current_path( + idv_socure_document_capture_update_path(docv_token: invalid_token), + ) + socure_docv_upload_documents( + docv_transaction_token: @docv_transaction_token, + ) + visit idv_socure_document_capture_update_path(docv_token: invalid_token) + + expect(page).to have_current_path(idv_ssn_url) + + expect(DocAuthLog.find_by(user_id: @user.id).state).to eq('NY') + + fill_out_ssn_form_ok + click_idv_continue + complete_verify_step + expect(page).to have_current_path(idv_phone_url) + end + end + end end context 'standard mobile flow' do it 'proceeds to the next page with valid info' do perform_in_browser(:mobile) do visit_idp_from_oidc_sp_with_ial2 - sign_in_and_2fa_user(@user) + @user = sign_in_and_2fa_user complete_doc_auth_steps_before_document_capture_step expect(page).to have_current_path(idv_socure_document_capture_url) @@ -164,10 +215,13 @@ shared_examples 'a properly categorized Socure error' do |socure_error_code, expected_header_key| before do - stub_docv_verification_data_fail_with([socure_error_code]) + stub_docv_verification_data_fail_with( + docv_transaction_token: @docv_transaction_token, + errors: [socure_error_code], + ) visit_idp_from_oidc_sp_with_ial2 - sign_in_and_2fa_user(@user) + @user = sign_in_and_2fa_user complete_doc_auth_steps_before_document_capture_step diff --git a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb index 0245ba60c5a..6bb6e0f8445 100644 --- a/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb +++ b/spec/features/idv/hybrid_mobile/hybrid_socure_mobile_spec.rb @@ -9,6 +9,7 @@ let(:sp) { :oidc } let(:fake_socure_document_capture_app_url) { 'https://verify.fake-socure.test/something' } let(:fake_socure_docv_document_request_endpoint) { 'https://fake-socure.test/document-request' } + let(:socure_docv_verification_data_test_mode) { false } before do allow(FeatureManagement).to receive(:doc_capture_polling_enabled?).and_return(true) @@ -23,10 +24,15 @@ @sms_link = config[:link] impl.call(**config) end.at_least(1).times + allow(IdentityConfig.store).to receive(:socure_docv_verification_data_test_mode). + and_return(socure_docv_verification_data_test_mode) @docv_transaction_token = stub_docv_document_request end context 'happy path' do + before do + @pass_stub = stub_docv_verification_data_pass(docv_transaction_token: @docv_transaction_token) + end it 'proofs and hands off to mobile', js: true do user = nil @@ -76,7 +82,6 @@ expect(page).to have_current_path(idv_hybrid_mobile_socure_document_capture_url) - stub_docv_verification_data_pass click_idv_continue expect(page).to have_current_path(fake_socure_document_capture_app_url) socure_docv_upload_documents(docv_transaction_token: @docv_transaction_token) @@ -190,7 +195,6 @@ click_idv_continue expect(page).to have_current_path(fake_socure_document_capture_app_url) - stub_docv_verification_data_pass max_attempts.times do socure_docv_upload_documents(docv_transaction_token: @docv_transaction_token) end @@ -225,7 +229,6 @@ visit @sms_link expect(page).to have_current_path(idv_hybrid_mobile_socure_document_capture_url) - stub_docv_verification_data_pass click_idv_continue expect(page).to have_current_path(fake_socure_document_capture_app_url) socure_docv_upload_documents(docv_transaction_token: @docv_transaction_token) @@ -253,6 +256,87 @@ ) end end + + context 'when socure_docv_verification_data_test_mode is enabled' do + let(:test_token) { 'valid-test-token' } + let(:socure_docv_verification_data_test_mode) { true } + before do + allow(IdentityConfig.store).to receive(:socure_docv_verification_data_test_mode_tokens). + and_return([test_token]) + DocAuth::Mock::DocAuthMockClient.reset! + end + + context 'when a valid test token is used' do + it 'fetches verificationdata using override docvToken in request', + js: true, allow_browser_log: true do + user = nil + + perform_in_browser(:desktop) do + visit_idp_from_sp_with_ial2(sp) + user = sign_up_and_2fa_ial1_user + + complete_doc_auth_steps_before_hybrid_handoff_step + click_send_link + end + + expect(@sms_link).to be_present + + perform_in_browser(:mobile) do + remove_request_stub(@pass_stub) + stub_docv_verification_data_pass(docv_transaction_token: test_token) + + visit @sms_link + + expect(page).to have_current_path(idv_hybrid_mobile_socure_document_capture_url) + + visit idv_hybrid_mobile_socure_document_capture_update_path(docv_token: test_token) + + expect(page).to have_current_path(idv_hybrid_mobile_capture_complete_url) + end + + perform_in_browser(:desktop) do + expect(page).to have_current_path(idv_ssn_path, wait: 10) + end + end + end + + context 'when an invalid test token is used' do + let(:invalid_token) { 'invalid-token' } + + it 'waits to fetch verificationdata using docv capture session token', js: true do + user = nil + + perform_in_browser(:desktop) do + visit_idp_from_sp_with_ial2(sp) + user = sign_up_and_2fa_ial1_user + + complete_doc_auth_steps_before_hybrid_handoff_step + click_send_link + end + + expect(@sms_link).to be_present + + perform_in_browser(:mobile) do + visit @sms_link + + click_idv_continue + + visit idv_hybrid_mobile_socure_document_capture_update_path(docv_token: invalid_token) + expect(page).to have_current_path( + idv_hybrid_mobile_socure_document_capture_update_path(docv_token: invalid_token), + ) + socure_docv_upload_documents(docv_transaction_token: @docv_transaction_token) + visit idv_hybrid_mobile_socure_document_capture_update_path(docv_token: invalid_token) + + expect(page).to have_current_path(idv_hybrid_mobile_capture_complete_path) + end + + perform_in_browser(:desktop) do + expect(page).to have_current_path(idv_ssn_path, wait: 10) + end + end + end + end end shared_examples 'a properly categorized Socure error' do |socure_error_code, expected_header_key| @@ -280,7 +364,10 @@ perform_in_browser(:mobile) do visit @sms_link - stub_docv_verification_data_fail_with([socure_error_code]) + stub_docv_verification_data_fail_with( + docv_transaction_token: @docv_transaction_token, + errors: [socure_error_code], + ) click_idv_continue diff --git a/spec/support/features/document_capture_step_helper.rb b/spec/support/features/document_capture_step_helper.rb index 9143136c3fe..72c40807522 100644 --- a/spec/support/features/document_capture_step_helper.rb +++ b/spec/support/features/document_capture_step_helper.rb @@ -103,16 +103,22 @@ def socure_docv_send_webhook( end end - def stub_docv_verification_data_pass - stub_docv_verification_data(body: SocureDocvFixtures.pass_json) + def stub_docv_verification_data_pass(docv_transaction_token:) + stub_docv_verification_data(body: SocureDocvFixtures.pass_json, docv_transaction_token:) end - def stub_docv_verification_data_fail_with(errors) - stub_docv_verification_data(body: SocureDocvFixtures.fail_json(errors)) + def stub_docv_verification_data_fail_with(docv_transaction_token:, errors:) + stub_docv_verification_data(body: SocureDocvFixtures.fail_json(errors), docv_transaction_token:) end - def stub_docv_verification_data(body:) + def stub_docv_verification_data(docv_transaction_token:, body:) + request_body = { + modules: ['documentverification'], + docvTransactionToken: docv_transaction_token, + } + stub_request(:post, "#{IdentityConfig.store.socure_idplus_base_url}/api/3.0/EmailAuthScore"). + with(body: request_body.to_json). to_return( headers: { 'Content-Type' => 'application/json', From 9d7e3eacac2f05f1ad287c2a3b0236139da52027 Mon Sep 17 00:00:00 2001 From: "Davida (she/they)" Date: Mon, 9 Dec 2024 08:43:29 -0500 Subject: [PATCH 11/14] Update report to use earlier event (#11570) * changelog: Internal, Analytics, Update signature query to use more accurate event --- lib/reporting/protocols_report.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reporting/protocols_report.rb b/lib/reporting/protocols_report.rb index fe1d333961a..560d7da79d7 100644 --- a/lib/reporting/protocols_report.rb +++ b/lib/reporting/protocols_report.rb @@ -17,6 +17,7 @@ class ProtocolsReport attr_reader :time_range SAML_AUTH_EVENT = 'SAML Auth' + SAML_AUTH_REQUEST_EVENT = 'SAML Auth Request' OIDC_AUTH_EVENT = 'OpenID Connect: authorization request' # @param [Range