From 389beb686ee140945608903e98da5d50c6d8202f Mon Sep 17 00:00:00 2001 From: Steve Urciuoli Date: Fri, 30 Mar 2018 11:37:19 -0400 Subject: [PATCH 01/39] LG-79 Turn on agency based UUIDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why**: We need to share agency-specific UUIDs as well as facilitate sharing of an agency鈥檚 UUID with other agencies. We also need a way to count the unique number of users for an agency for billing purposes **How** Change the feature flags associated with agency based uuids. Specifically we are changing the environment variables enable_agency_based_uuids and agencies_with_agency_based_uuids. --- config/application.yml.example | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/application.yml.example b/config/application.yml.example index b19b3a85ad8..d118ed1c8e7 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -63,7 +63,7 @@ development: aamva_public_key: '123abc' aamva_private_key: '123abc' aamva_verification_url: 'https://example.org:12345/verification/url' - agencies_with_agency_based_uuids: '' + agencies_with_agency_based_uuids: '1,2,3,4,5' async_job_refresh_interval_seconds: '5' async_job_refresh_max_wait_seconds: '15' attribute_cost: '4000$8$4$' # SCrypt::Engine.calibrate(max_time: 0.5) @@ -87,7 +87,7 @@ development: database_timeout: '5000' database_username: '' domain_name: 'localhost:3000' - enable_agency_based_uuids: 'false' + enable_agency_based_uuids: 'true' enable_identity_verification: 'true' enable_rate_limiting: 'false' enable_test_routes: 'true' @@ -160,7 +160,7 @@ production: aamva_public_key: # Base64 encoded public key for AAMVA aamva_private_key: # Base64 encoded private key for AAMVA aamva_verification_url: # DLDV Verification URL - agencies_with_agency_based_uuids: '' + agencies_with_agency_based_uuids: '1,2,3,4,5' async_job_refresh_interval_seconds: '5' async_job_refresh_max_wait_seconds: '15' attribute_cost: '4000$8$4$' # SCrypt::Engine.calibrate(max_time: 0.5) @@ -175,7 +175,7 @@ production: disable_email_sending: 'false' dashboard_api_token: domain_name: 'login.gov' - enable_agency_based_uuids: 'false' + enable_agency_based_uuids: 'true' enable_identity_verification: 'false' enable_rate_limiting: 'true' enable_test_routes: 'false' @@ -246,7 +246,7 @@ test: aamva_public_key: '123abc' aamva_private_key: '123abc' aamva_verification_url: 'https://example.org:12345/verification/url' - agencies_with_agency_based_uuids: '' + agencies_with_agency_based_uuids: '1,2,3,4,5' async_job_refresh_interval_seconds: '1' async_job_refresh_max_wait_seconds: '15' attribute_cost: '800$8$1$' # SCrypt::Engine.calibrate(max_time: 0.01) @@ -269,7 +269,7 @@ test: database_timeout: '5000' database_username: '' dashboard_api_token: '123ABC' - enable_agency_based_uuids: 'false' + enable_agency_based_uuids: 'true' enable_identity_verification: 'true' enable_rate_limiting: 'true' enable_test_routes: 'true' From 5c4ad8ae84598e67b25ff05489dc82fbe2dd21f8 Mon Sep 17 00:00:00 2001 From: David Corwin Date: Tue, 10 Apr 2018 11:11:35 -0700 Subject: [PATCH 02/39] Remove vendor session id from idv session and proofing flow (#2089) * Remove vendor session id from idv session and proofing flow **WHY** We no longer need to track a vendor session across multiple proofing requests, each request will be handled independently --- app/jobs/idv/phone_job.rb | 2 +- app/jobs/idv/profile_job.rb | 3 +-- app/jobs/idv/proofer_job.rb | 5 ++--- app/services/idv/profile_step.rb | 1 - app/services/idv/session.rb | 1 - app/services/idv/submit_idv_job.rb | 1 - app/services/idv/vendor_result.rb | 5 ++--- spec/jobs/idv/phone_job_spec.rb | 5 ----- spec/jobs/idv/profile_job_spec.rb | 3 --- spec/services/idv/submit_idv_job_spec.rb | 4 ---- spec/services/idv/vendor_result_spec.rb | 3 --- spec/services/vendor_validator_result_storage_spec.rb | 2 -- 12 files changed, 6 insertions(+), 29 deletions(-) diff --git a/app/jobs/idv/phone_job.rb b/app/jobs/idv/phone_job.rb index 6646ab12c5d..101084bdfc3 100644 --- a/app/jobs/idv/phone_job.rb +++ b/app/jobs/idv/phone_job.rb @@ -1,7 +1,7 @@ module Idv class PhoneJob < ProoferJob def verify_identity_with_vendor - confirmation = agent.submit_phone(vendor_params, vendor_session_id) + confirmation = agent.submit_phone(vendor_params) result = extract_result(confirmation) store_result(result) end diff --git a/app/jobs/idv/profile_job.rb b/app/jobs/idv/profile_job.rb index db35d44b65e..bee7e03b7b3 100644 --- a/app/jobs/idv/profile_job.rb +++ b/app/jobs/idv/profile_job.rb @@ -54,8 +54,7 @@ def build_and_store_result success: result_successful?, errors: result_errors, reasons: result_reasons, - normalized_applicant: resolution_vendor_resp.normalized_applicant, - session_id: resolution.session_id + normalized_applicant: resolution_vendor_resp.normalized_applicant ) store_result(result) end diff --git a/app/jobs/idv/proofer_job.rb b/app/jobs/idv/proofer_job.rb index c2096db60a0..b0d6865db85 100644 --- a/app/jobs/idv/proofer_job.rb +++ b/app/jobs/idv/proofer_job.rb @@ -2,13 +2,12 @@ module Idv class ProoferJob < ApplicationJob queue_as :idv - attr_reader :result_id, :applicant, :vendor_params, :vendor_session_id + attr_reader :result_id, :applicant, :vendor_params - def perform(result_id:, vendor_params:, applicant_json:, vendor_session_id: nil) + def perform(result_id:, vendor_params:, applicant_json:) @result_id = result_id @vendor_params = vendor_params @applicant = applicant_from_json(applicant_json) - @vendor_session_id = vendor_session_id perform_identity_proofing end diff --git a/app/services/idv/profile_step.rb b/app/services/idv/profile_step.rb index 371e6d2a71a..aba09faf599 100644 --- a/app/services/idv/profile_step.rb +++ b/app/services/idv/profile_step.rb @@ -31,7 +31,6 @@ def increment_attempts_count def update_idv_session idv_session.profile_confirmation = true - idv_session.vendor_session_id = vendor_validator_result.session_id idv_session.normalized_applicant_params = vendor_validator_result.normalized_applicant.to_hash idv_session.resolution_successful = true end diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index c53f6d6a990..74d567bd796 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -18,7 +18,6 @@ class Session personal_key resolution_successful step_attempts - vendor_session_id ].freeze attr_reader :current_user, :usps_otp diff --git a/app/services/idv/submit_idv_job.rb b/app/services/idv/submit_idv_job.rb index a2806e43601..d686bda4b9a 100644 --- a/app/services/idv/submit_idv_job.rb +++ b/app/services/idv/submit_idv_job.rb @@ -23,7 +23,6 @@ def proofer_job_params { result_id: result_id, vendor_params: vendor_params, - vendor_session_id: idv_session.vendor_session_id, applicant_json: idv_session.applicant.to_json, } end diff --git a/app/services/idv/vendor_result.rb b/app/services/idv/vendor_result.rb index a1a528b2b61..f8457614dfb 100644 --- a/app/services/idv/vendor_result.rb +++ b/app/services/idv/vendor_result.rb @@ -1,6 +1,6 @@ module Idv class VendorResult - attr_reader :success, :errors, :reasons, :session_id, :normalized_applicant, :timed_out + attr_reader :success, :errors, :reasons, :normalized_applicant, :timed_out def self.new_from_json(json) parsed = JSON.parse(json, symbolize_names: true) @@ -11,12 +11,11 @@ def self.new_from_json(json) new(**parsed) end - def initialize(success: nil, errors: {}, reasons: [], session_id: nil, + def initialize(success: nil, errors: {}, reasons: [], normalized_applicant: nil, timed_out: nil) @success = success @errors = errors @reasons = reasons - @session_id = session_id @normalized_applicant = normalized_applicant @timed_out = timed_out end diff --git a/spec/jobs/idv/phone_job_spec.rb b/spec/jobs/idv/phone_job_spec.rb index ad84e4e6c5e..72cfd474887 100644 --- a/spec/jobs/idv/phone_job_spec.rb +++ b/spec/jobs/idv/phone_job_spec.rb @@ -7,7 +7,6 @@ let(:result_id) { SecureRandom.uuid } let(:applicant_json) { { first_name: 'Jean-Luc', last_name: 'Picard' }.to_json } let(:vendor_params) { '5555550000' } - let(:vendor_session_id) { SecureRandom.uuid } context 'when verification succeeds' do it 'should save a successful result' do @@ -15,7 +14,6 @@ result_id: result_id, vendor_params: vendor_params, applicant_json: applicant_json, - vendor_session_id: vendor_session_id ) result = VendorValidatorResultStorage.new.load(result_id) @@ -35,7 +33,6 @@ result_id: result_id, vendor_params: vendor_params, applicant_json: applicant_json, - vendor_session_id: vendor_session_id ) result = VendorValidatorResultStorage.new.load(result_id) @@ -60,7 +57,6 @@ result_id: result_id, vendor_params: vendor_params, applicant_json: applicant_json, - vendor_session_id: vendor_session_id ) end.to raise_error(RuntimeError, '馃敟馃敟馃敟') result = VendorValidatorResultStorage.new.load(result_id) @@ -78,7 +74,6 @@ result_id: result_id, vendor_params: vendor_params, applicant_json: applicant_json, - vendor_session_id: vendor_session_id ) result = VendorValidatorResultStorage.new.load(result_id) diff --git a/spec/jobs/idv/profile_job_spec.rb b/spec/jobs/idv/profile_job_spec.rb index aba909dd45b..082f4b35629 100644 --- a/spec/jobs/idv/profile_job_spec.rb +++ b/spec/jobs/idv/profile_job_spec.rb @@ -50,7 +50,6 @@ expect(result.normalized_applicant.last_name).to eq('PICARD') expect(result.reasons).to eq(['Everything looks good', 'valid state ID']) expect(result.errors).to eq({}) - expect(result.session_id).to be_present end end @@ -74,7 +73,6 @@ expect(result.job_failed?).to eq(false) expect(result.reasons).to eq(['The name was suspicious']) expect(result.errors).to eq(first_name: 'Unverified first name.') - expect(result.session_id).to be_present expect(agent).to have_received(:start) expect(agent).to_not have_received(:submit_state_id) end @@ -96,7 +94,6 @@ expect(result.job_failed?).to eq(false) expect(result.reasons).to eq(['Everything looks good', 'invalid state id number']) expect(result.errors).to eq(state_id_number: 'The state ID number could not be verified') - expect(result.session_id).to be_present end end diff --git a/spec/services/idv/submit_idv_job_spec.rb b/spec/services/idv/submit_idv_job_spec.rb index f368f1256f4..7d4530bc8d3 100644 --- a/spec/services/idv/submit_idv_job_spec.rb +++ b/spec/services/idv/submit_idv_job_spec.rb @@ -15,7 +15,6 @@ user_session: { idv: { applicant: applicant, - vendor_session_id: vendor_session_id, }, } ) @@ -23,7 +22,6 @@ let(:user) { build(:user) } let(:applicant) { Proofer::Applicant.new(first_name: 'Greatest') } - let(:vendor_session_id) { '12345' } let(:result_id) { 'abcdef' } let(:vendor_params) { { dob: '01/01/1985' } } @@ -33,7 +31,6 @@ with( result_id: result_id, vendor_params: vendor_params, - vendor_session_id: vendor_session_id, applicant_json: applicant.to_json ) @@ -57,7 +54,6 @@ with( result_id: result_id, vendor_params: vendor_params, - vendor_session_id: vendor_session_id, applicant_json: applicant.to_json ) diff --git a/spec/services/idv/vendor_result_spec.rb b/spec/services/idv/vendor_result_spec.rb index 74d39e2809f..7d31b33dea1 100644 --- a/spec/services/idv/vendor_result_spec.rb +++ b/spec/services/idv/vendor_result_spec.rb @@ -4,7 +4,6 @@ let(:success) { true } let(:errors) { { foo: ['is not valid'] } } let(:reasons) { %w[foo bar baz] } - let(:session_id) { SecureRandom.uuid } let(:normalized_applicant) do Proofer::Applicant.new( last_name: 'Ever', @@ -18,7 +17,6 @@ success: success, errors: errors, reasons: reasons, - session_id: session_id, normalized_applicant: normalized_applicant, timed_out: timed_out ) @@ -52,7 +50,6 @@ expect(new_from_json.success?).to eq(vendor_result.success?) expect(new_from_json.errors).to eq(vendor_result.errors) expect(new_from_json.reasons).to eq(vendor_result.reasons) - expect(new_from_json.session_id).to eq(vendor_result.session_id) end it 'turns applicant into a full object' do diff --git a/spec/services/vendor_validator_result_storage_spec.rb b/spec/services/vendor_validator_result_storage_spec.rb index 6cd36a9270f..3ba45b313a9 100644 --- a/spec/services/vendor_validator_result_storage_spec.rb +++ b/spec/services/vendor_validator_result_storage_spec.rb @@ -3,12 +3,10 @@ RSpec.describe VendorValidatorResultStorage do subject(:service) { VendorValidatorResultStorage.new } - let(:session_id) { SecureRandom.uuid } let(:result_id) { SecureRandom.uuid } let(:original_result) do Idv::VendorResult.new( success: false, - session_id: session_id, normalized_applicant: Proofer::Applicant.new(first_name: 'First') ) end From d6fda9fbfd57e03ab693a36d60261946d55e95f6 Mon Sep 17 00:00:00 2001 From: James Smith Date: Mon, 9 Apr 2018 16:02:00 -0400 Subject: [PATCH 03/39] Add the uuid for a x509 subject to the users table **Why**: - We want to track which PIV/CAC a user associates with their account. - We don't want to record PII -- the PIV/CAC public certificates have a DN which contains the user's name. **How**: - We use a UUID created by the identity piv/cac management micro- service. This will be randomly generated the first time the system encounters a given PIV/CAC, assuming the certificate is valid. --- app/models/user.rb | 2 ++ .../20180409193120_add_x509_dn_uuid_to_users_table.rb | 11 +++++++++++ db/schema.rb | 4 +++- spec/factories/users.rb | 4 ++++ spec/models/user_spec.rb | 8 ++++++++ 5 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20180409193120_add_x509_dn_uuid_to_users_table.rb diff --git a/app/models/user.rb b/app/models/user.rb index a8d17d9c12e..10342010e04 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -36,6 +36,8 @@ class User < ApplicationRecord has_many :profiles, dependent: :destroy has_many :events, dependent: :destroy + validates :x509_dn_uuid, uniqueness: true, allow_nil: true + attr_accessor :asserted_attributes def personal_key diff --git a/db/migrate/20180409193120_add_x509_dn_uuid_to_users_table.rb b/db/migrate/20180409193120_add_x509_dn_uuid_to_users_table.rb new file mode 100644 index 00000000000..89a4ce1d975 --- /dev/null +++ b/db/migrate/20180409193120_add_x509_dn_uuid_to_users_table.rb @@ -0,0 +1,11 @@ +class AddX509DnUuidToUsersTable < ActiveRecord::Migration[5.1] + def up + add_column :users, :x509_dn_uuid, :string + add_index :users, :x509_dn_uuid, unique: true + end + + def down + remove_index :users, :x509_dn_uuid + remove_column :users, :x509_dn_uuid + end +end diff --git a/db/schema.rb b/db/schema.rb index b2bd6e345e0..3c83dbbead1 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.define(version: 20180201161105) do +ActiveRecord::Schema.define(version: 20180409193120) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -179,6 +179,7 @@ t.text "encrypted_phone" t.integer "otp_delivery_preference", default: 0, null: false t.integer "totp_timestamp" + t.string "x509_dn_uuid" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email_fingerprint"], name: "index_users_on_email_fingerprint", unique: true t.index ["encrypted_otp_secret_key"], name: "index_users_on_encrypted_otp_secret_key", unique: true @@ -186,6 +187,7 @@ t.index ["unconfirmed_email"], name: "index_users_on_unconfirmed_email" t.index ["unlock_token"], name: "index_users_on_unlock_token" t.index ["uuid"], name: "index_users_on_uuid", unique: true + t.index ["x509_dn_uuid"], name: "index_users_on_x509_dn_uuid", unique: true end create_table "usps_confirmation_codes", force: :cascade do |t| diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 9be92510ea2..c69990d0a18 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -11,6 +11,10 @@ phone_confirmed_at Time.zone.now end + trait :with_piv_or_cac do + x509_dn_uuid { SecureRandom.uuid } + end + trait :admin do role :admin end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b7da78ae3f1..67c23490e5b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -270,4 +270,12 @@ expect(User.find_with_email(foo: 'bar')).to eq(nil) end end + + describe 'x509_dn_uuid' do + it 'validates uniqueness' do + user = create(:user, :with_piv_or_cac, email: 'test1@test.com') + + expect(user).to validate_uniqueness_of(:x509_dn_uuid).allow_nil + end + end end From 41365278780e08f63b4c9cce6b59696058ae3cd3 Mon Sep 17 00:00:00 2001 From: Steve Urciuoli Date: Tue, 10 Apr 2018 15:46:37 -0400 Subject: [PATCH 04/39] LG-170 Log user out when deleting account **Why**: We need to log the user out before deleting the current user to destroy the session. **How** Add sign_out to the delete method. --- app/controllers/users/delete_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/users/delete_controller.rb b/app/controllers/users/delete_controller.rb index 0a526b38844..d32b323a365 100644 --- a/app/controllers/users/delete_controller.rb +++ b/app/controllers/users/delete_controller.rb @@ -6,6 +6,7 @@ def show; end def delete current_user.destroy! + sign_out flash[:success] = t('devise.registrations.destroyed') redirect_to root_url end From ead79b63c529c7c41828c001a40ae404dcf68f5e Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Wed, 11 Apr 2018 13:26:55 -0500 Subject: [PATCH 05/39] Break trailing comma cop into hash and array cop (#2054) **Why**: The latest version of rubocop has removed the trailing comma cop in favor of using specific cops for hashes and arrays. - `Naming/MemoizedInstanceVariableName` is disabled to allow for instance variables prefixed with `_` - `Naming/UncommunicativeMethodParamName` is set to `2` to support keyword argument name `sp` - `Style/ExpandPathArguments` is disabled to not change the `File.expand_path` calls but we may want to revisit --- .codeclimate.yml | 1 + .rubocop.yml | 22 +++++++++++++++++++++- Gemfile | 2 +- Gemfile.lock | 6 +++--- app/services/encrypted_attribute.rb | 2 +- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 6b18c99e9fe..e8d2947ad0e 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -83,6 +83,7 @@ plugins: - 'lib/rspec/formatters/user_flow_formatter.rb' rubocop: enabled: true + channel: rubocop-0-54 scss-lint: enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index 32228d61128..4113674c9e0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -278,7 +278,18 @@ Style/TrailingCommaInArguments: - consistent_comma - no_comma -Style/TrailingCommaInLiteral: +Style/TrailingCommaInArrayLiteral: + # If `comma`, the cop requires a comma after the last item in an array or + # hash, but only when each item is on its own line. + # If `consistent_comma`, the cop requires a comma after the last item of all + # non-empty array and hash literals. + EnforcedStyleForMultiline: comma + SupportedStylesForMultiline: + - comma + - consistent_comma + - no_comma + +Style/TrailingCommaInHashLiteral: # If `comma`, the cop requires a comma after the last item in an array or # hash, but only when each item is on its own line. # If `consistent_comma`, the cop requires a comma after the last item of all @@ -291,3 +302,12 @@ Style/TrailingCommaInLiteral: Style/SingleLineBlockParams: Enabled: false + +Naming/MemoizedInstanceVariableName: + Enabled: false + +Naming/UncommunicativeMethodParamName: + MinNameLength: 2 + +Style/ExpandPathArguments: + Enabled: false diff --git a/Gemfile b/Gemfile index d6a401e1ab5..f1f88c267d4 100644 --- a/Gemfile +++ b/Gemfile @@ -74,7 +74,7 @@ group :development do gem 'rack-mini-profiler', require: false gem 'rails-erd' gem 'reek' - gem 'rubocop', '~> 0.52.0', require: false + gem 'rubocop', '~> 0.54.0', require: false end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 3973a054aee..24c8bcbac04 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -489,9 +489,9 @@ GEM rspec-mocks (~> 3.5.0) rspec-support (~> 3.5.0) rspec-support (3.5.0) - rubocop (0.52.1) + rubocop (0.54.0) parallel (~> 1.10) - parser (>= 2.4.0.2, < 3.0) + parser (>= 2.5) powerpack (~> 0.1) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) @@ -723,7 +723,7 @@ DEPENDENCIES reek rqrcode rspec-rails (~> 3.5.2) - rubocop (~> 0.52.0) + rubocop (~> 0.54.0) ruby-progressbar ruby-saml saml_idp! diff --git a/app/services/encrypted_attribute.rb b/app/services/encrypted_attribute.rb index db47af96cd5..d0e3a087781 100644 --- a/app/services/encrypted_attribute.rb +++ b/app/services/encrypted_attribute.rb @@ -69,7 +69,7 @@ def try_decrypt_with_all_keys(encryptor, cost) end def try_decrypt_with_uak(encryptor) - return encryptor.decrypt(encrypted, user_access_key) + encryptor.decrypt(encrypted, user_access_key) rescue Pii::EncryptionError => _err nil end From cd6f243947a1831c5211f03e624750ea8572f986 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Thu, 12 Apr 2018 13:30:16 -0500 Subject: [PATCH 06/39] Break account creation spec into 2 specs (#2090) **Why**: The account creation spec looked like it was really testing that the handoff to the SP is successful and that the user verifies the attributes requested by the SP before handoff. This commit breaks that spec into 2 separate specs to test those things specifically. --- spec/features/idv/account_creation_spec.rb | 5 - spec/features/idv/sp_handoff_spec.rb | 14 ++ .../idv/sp_requested_attributes_spec.rb | 11 + spec/support/idv_examples/account_creation.rb | 130 ----------- spec/support/idv_examples/sp_handoff.rb | 219 ++++++++++++++++++ .../idv_examples/sp_requested_attributes.rb | 67 ++++++ 6 files changed, 311 insertions(+), 135 deletions(-) create mode 100644 spec/features/idv/sp_handoff_spec.rb create mode 100644 spec/features/idv/sp_requested_attributes_spec.rb delete mode 100644 spec/support/idv_examples/account_creation.rb create mode 100644 spec/support/idv_examples/sp_handoff.rb create mode 100644 spec/support/idv_examples/sp_requested_attributes.rb diff --git a/spec/features/idv/account_creation_spec.rb b/spec/features/idv/account_creation_spec.rb index 6347bd1f9dd..938e8096194 100644 --- a/spec/features/idv/account_creation_spec.rb +++ b/spec/features/idv/account_creation_spec.rb @@ -4,11 +4,6 @@ include SamlAuthHelper include IdvHelper - context 'successful IdV with same phone as 2FA' do - it_behaves_like 'idv account creation', :saml - it_behaves_like 'idv account creation', :oidc - end - context 'choosing USPS address verification' do it_behaves_like 'selecting usps address verification method', :saml it_behaves_like 'selecting usps address verification method', :oidc diff --git a/spec/features/idv/sp_handoff_spec.rb b/spec/features/idv/sp_handoff_spec.rb new file mode 100644 index 00000000000..64f68dcb864 --- /dev/null +++ b/spec/features/idv/sp_handoff_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +feature 'IdV SP handoff', :idv_job, :email do + include SamlAuthHelper + include IdvHelper + + context 'with oidc' do + it_behaves_like 'sp handoff after identity verification', :oidc + end + + context 'with saml' do + it_behaves_like 'sp handoff after identity verification', :saml + end +end diff --git a/spec/features/idv/sp_requested_attributes_spec.rb b/spec/features/idv/sp_requested_attributes_spec.rb new file mode 100644 index 00000000000..bf383f07f01 --- /dev/null +++ b/spec/features/idv/sp_requested_attributes_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +feature 'sp requested IdV attributes', :idv_job, :email do + context 'oidc' do + it_behaves_like 'sp requesting attributes', :oidc + end + + context 'saml' do + it_behaves_like 'sp requesting attributes', :saml + end +end diff --git a/spec/support/idv_examples/account_creation.rb b/spec/support/idv_examples/account_creation.rb deleted file mode 100644 index ebfa4c38a1f..00000000000 --- a/spec/support/idv_examples/account_creation.rb +++ /dev/null @@ -1,130 +0,0 @@ -shared_examples 'idv account creation' do |sp| - it 'redirects to SP after IdV is complete', email: true do - allow(Figaro.env).to receive(:enable_agency_based_uuids).and_return('true') - allow(Figaro.env).to receive(:agencies_with_agency_based_uuids).and_return('1,2,3') - email = 'test@test.com' - - visit_idp_from_sp_with_loa3(sp) - - register_user(email) - - expect(current_path).to eq verify_path - - click_idv_begin - - user = User.find_with_email(email) - complete_idv_profile_ok(user.reload) - click_acknowledge_personal_key - - expect(page).to have_content t( - 'titles.sign_up.verified', - app: APP_NAME - ) - within('.requested-attributes') do - expect(page).to have_content t('help_text.requested_attributes.email') - expect(page).to_not have_content t('help_text.requested_attributes.address') - expect(page).to_not have_content t('help_text.requested_attributes.birthdate') - expect(page).to have_content t('help_text.requested_attributes.full_name') - expect(page).to have_content t('help_text.requested_attributes.phone') - expect(page).to have_content t('help_text.requested_attributes.social_security_number') - end - - if sp == :oidc - expect(page.response_headers['Content-Security-Policy']). - to(include('form-action \'self\' http://localhost:7654')) - end - - click_on I18n.t('forms.buttons.continue') - - expect(user.events.account_verified.size).to be(1) - - if sp == :saml - user_access_key = user.unlock_user_access_key(Features::SessionHelper::VALID_PASSWORD) - profile_phone = user.active_profile.decrypt_pii(user_access_key).phone - xmldoc = SamlResponseDoc.new('feature', 'response_assertion') - - expect(AgencyIdentity.where(user_id: user.id, agency_id: 2).first.uuid).to eq(xmldoc.uuid) - expect(current_url).to eq @saml_authn_request - expect(xmldoc.phone_number.children.children.to_s).to eq(profile_phone) - end - - if sp == :oidc - redirect_uri = URI(current_url) - redirect_params = Rack::Utils.parse_query(redirect_uri.query).with_indifferent_access - - expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result') - expect(redirect_params[:state]).to eq(@state) - - code = redirect_params[:code] - expect(code).to be_present - - jwt_payload = { - iss: @client_id, - sub: @client_id, - aud: api_openid_connect_token_url, - jti: SecureRandom.hex, - exp: 5.minutes.from_now.to_i, - } - - client_assertion = JWT.encode(jwt_payload, client_private_key, 'RS256') - client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' - - page.driver.post api_openid_connect_token_path, - grant_type: 'authorization_code', - code: code, - client_assertion_type: client_assertion_type, - client_assertion: client_assertion - - expect(page.status_code).to eq(200) - token_response = JSON.parse(page.body).with_indifferent_access - - id_token = token_response[:id_token] - expect(id_token).to be_present - - decoded_id_token, _headers = JWT.decode( - id_token, sp_public_key, true, algorithm: 'RS256' - ).map(&:with_indifferent_access) - - sub = decoded_id_token[:sub] - expect(sub).to be_present - expect(decoded_id_token[:nonce]).to eq(@nonce) - expect(decoded_id_token[:aud]).to eq(@client_id) - expect(decoded_id_token[:acr]).to eq(Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF) - expect(decoded_id_token[:iss]).to eq(root_url) - expect(decoded_id_token[:email]).to eq(user.email) - expect(decoded_id_token[:given_name]).to eq('Jos茅') - expect(decoded_id_token[:social_security_number]).to eq('666-66-1234') - - access_token = token_response[:access_token] - expect(access_token).to be_present - - page.driver.get api_openid_connect_userinfo_path, - {}, - 'HTTP_AUTHORIZATION' => "Bearer #{access_token}" - - userinfo_response = JSON.parse(page.body).with_indifferent_access - expect(userinfo_response[:sub]).to eq(sub) - expect(AgencyIdentity.where(user_id: user.id, agency_id: 2).first.uuid).to eq(sub) - expect(userinfo_response[:email]).to eq(user.email) - expect(userinfo_response[:given_name]).to eq('Jos茅') - expect(userinfo_response[:social_security_number]).to eq('666-66-1234') - end - end -end - -def client_private_key - @client_private_key ||= begin - OpenSSL::PKey::RSA.new( - File.read(Rails.root.join('keys', 'saml_test_sp.key')) - ) - end -end - -def sp_public_key - page.driver.get api_openid_connect_certs_path - - expect(page.status_code).to eq(200) - certs_response = JSON.parse(page.body).with_indifferent_access - - JSON::JWK.new(certs_response[:keys].first).to_key -end diff --git a/spec/support/idv_examples/sp_handoff.rb b/spec/support/idv_examples/sp_handoff.rb new file mode 100644 index 00000000000..b8f5d608796 --- /dev/null +++ b/spec/support/idv_examples/sp_handoff.rb @@ -0,0 +1,219 @@ +shared_examples 'sp handoff after identity verification' do |sp| + include SamlAuthHelper + include IdvHelper + + before do + allow(Figaro.env).to receive(:enable_agency_based_uuids).and_return('true') + allow(Figaro.env).to receive(:agencies_with_agency_based_uuids).and_return('1,2,3') + allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true) + end + + let(:email) { 'test@test.com' } + + context 'sign up' do + let(:user) { User.find_with_email(email) } + + it 'requires idv and hands off correctly' do + visit_idp_from_sp_with_loa3(sp) + register_user(email) + + expect(current_path).to eq verify_path + + click_idv_begin + complete_idv_profile_ok(user) + click_acknowledge_personal_key + + expect(page).to have_content t( + 'titles.sign_up.verified', + app: APP_NAME + ) + expect_csp_headers_to_be_present if sp == :oidc + + click_on I18n.t('forms.buttons.continue') + + expect(user.events.account_verified.size).to be(1) + expect_successful_oidc_handoff if sp == :oidc + expect_successful_saml_handoff if sp == :saml + end + end + + context 'unverified user sign in' do + let(:user) { user_with_2fa } + + it 'requires idv and hands off successfully' do + visit_idp_from_sp_with_loa3(sp) + click_link t('links.sign_in') + sign_in_user(user) + click_submit_default + + expect(current_path).to eq verify_path + + click_idv_begin + complete_idv_profile_ok(user) + click_acknowledge_personal_key + + expect(page).to have_content t( + 'titles.sign_up.verified', + app: APP_NAME + ) + expect_csp_headers_to_be_present if sp == :oidc + + click_on I18n.t('forms.buttons.continue') + + expect(user.events.account_verified.size).to be(1) + expect_successful_oidc_handoff if sp == :oidc + expect_successful_saml_handoff if sp == :saml + end + end + + context 'verified user sign in' do + let(:user) { user_with_2fa } + + before do + sign_in_and_2fa_user(user) + visit verify_session_path + complete_idv_profile_ok(user) + click_acknowledge_personal_key + first(:link, t('links.sign_out')).click + end + + it 'does not require verification and hands off successfully' do + visit_idp_from_sp_with_loa3(sp) + click_link t('links.sign_in') + sign_in_user(user) + click_submit_default + + expect_csp_headers_to_be_present if sp == :oidc + + click_on I18n.t('forms.buttons.continue') + + expect_successful_oidc_handoff if sp == :oidc + expect_successful_saml_handoff if sp == :saml + end + end + + context 'second time a user signs in to an SP' do + let(:user) { user_with_2fa } + + before do + visit_idp_from_sp_with_loa3(sp) + click_link t('links.sign_in') + sign_in_user(user) + click_submit_default + click_idv_begin + complete_idv_profile_ok(user) + click_acknowledge_personal_key + click_on I18n.t('forms.buttons.continue') + visit account_path + first(:link, t('links.sign_out')).click + click_submit_default if sp == :saml # SAML SLO request + end + + it 'does not require idv or requested attribute verification and hands off successfully' do + visit_idp_from_sp_with_loa3(sp) + click_link t('links.sign_in') + sign_in_user(user) + + expect_csp_headers_to_be_present if sp == :oidc + + click_submit_default + + expect_successful_oidc_handoff if sp == :oidc + expect_successful_saml_handoff if sp == :saml + end + end + + def expect_csp_headers_to_be_present + expect(page.response_headers['Content-Security-Policy']). + to(include('form-action \'self\' http://localhost:7654')) + end + + def expect_successful_oidc_handoff + redirect_uri = URI(current_url) + redirect_params = Rack::Utils.parse_query(redirect_uri.query).with_indifferent_access + + expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result') + expect(redirect_params[:state]).to eq(@state) + + code = redirect_params[:code] + expect(code).to be_present + + jwt_payload = { + iss: @client_id, + sub: @client_id, + aud: api_openid_connect_token_url, + jti: SecureRandom.hex, + exp: 5.minutes.from_now.to_i, + } + + client_assertion = JWT.encode(jwt_payload, client_private_key, 'RS256') + client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' + + page.driver.post api_openid_connect_token_path, + grant_type: 'authorization_code', + code: code, + client_assertion_type: client_assertion_type, + client_assertion: client_assertion + + expect(page.status_code).to eq(200) + token_response = JSON.parse(page.body).with_indifferent_access + + id_token = token_response[:id_token] + expect(id_token).to be_present + + decoded_id_token, _headers = JWT.decode( + id_token, sp_public_key, true, algorithm: 'RS256' + ).map(&:with_indifferent_access) + + sub = decoded_id_token[:sub] + expect(sub).to be_present + expect(decoded_id_token[:nonce]).to eq(@nonce) + expect(decoded_id_token[:aud]).to eq(@client_id) + expect(decoded_id_token[:acr]).to eq(Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF) + expect(decoded_id_token[:iss]).to eq(root_url) + expect(decoded_id_token[:email]).to eq(user.email) + expect(decoded_id_token[:given_name]).to eq('Jos茅') + expect(decoded_id_token[:social_security_number]).to eq('666-66-1234') + + access_token = token_response[:access_token] + expect(access_token).to be_present + + page.driver.get api_openid_connect_userinfo_path, + {}, + 'HTTP_AUTHORIZATION' => "Bearer #{access_token}" + + userinfo_response = JSON.parse(page.body).with_indifferent_access + expect(userinfo_response[:sub]).to eq(sub) + expect(AgencyIdentity.where(user_id: user.id, agency_id: 2).first.uuid).to eq(sub) + expect(userinfo_response[:email]).to eq(user.email) + expect(userinfo_response[:given_name]).to eq('Jos茅') + expect(userinfo_response[:social_security_number]).to eq('666-66-1234') + end + + def expect_successful_saml_handoff + user_access_key = user.unlock_user_access_key(Features::SessionHelper::VALID_PASSWORD) + profile_phone = user.active_profile.decrypt_pii(user_access_key).phone + xmldoc = SamlResponseDoc.new('feature', 'response_assertion') + + expect(AgencyIdentity.where(user_id: user.id, agency_id: 2).first.uuid).to eq(xmldoc.uuid) + expect(current_url).to eq @saml_authn_request + expect(xmldoc.phone_number.children.children.to_s).to eq(profile_phone) + end + + def client_private_key + @client_private_key ||= begin + OpenSSL::PKey::RSA.new( + File.read(Rails.root.join('keys', 'saml_test_sp.key')) + ) + end + end + + def sp_public_key + page.driver.get api_openid_connect_certs_path + + expect(page.status_code).to eq(200) + certs_response = JSON.parse(page.body).with_indifferent_access + + JSON::JWK.new(certs_response[:keys].first).to_key + end +end diff --git a/spec/support/idv_examples/sp_requested_attributes.rb b/spec/support/idv_examples/sp_requested_attributes.rb new file mode 100644 index 00000000000..2c4710edb17 --- /dev/null +++ b/spec/support/idv_examples/sp_requested_attributes.rb @@ -0,0 +1,67 @@ +shared_examples 'sp requesting attributes' do |sp| + include SamlAuthHelper + include IdvHelper + + before do + allow(Figaro.env).to receive(:enable_agency_based_uuids).and_return('true') + allow(Figaro.env).to receive(:agencies_with_agency_based_uuids).and_return('1,2,3') + allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true) + end + + let(:user) { user_with_2fa } + + context 'visiting an SP for the first time' do + it 'requires the user to verify the attributes submitted to the SP' do + visit_idp_from_sp_with_loa3(sp) + click_link t('links.sign_in') + sign_in_user(user) + click_submit_default + + expect(current_path).to eq verify_path + + click_idv_begin + complete_idv_profile_ok(user) + click_acknowledge_personal_key + + expect(current_path).to eq(sign_up_completed_path) + + within('.requested-attributes') do + expect(page).to have_content t('help_text.requested_attributes.email') + expect(page).to_not have_content t('help_text.requested_attributes.address') + expect(page).to_not have_content t('help_text.requested_attributes.birthdate') + expect(page).to have_content t('help_text.requested_attributes.full_name') + expect(page).to have_content t('help_text.requested_attributes.phone') + expect(page).to have_content t('help_text.requested_attributes.social_security_number') + end + end + end + + context 'visiting an SP the user has already signed into' do + before do + visit_idp_from_sp_with_loa3(sp) + click_link t('links.sign_in') + sign_in_user(user) + click_submit_default + click_idv_begin + complete_idv_profile_ok(user) + click_acknowledge_personal_key + click_on I18n.t('forms.buttons.continue') + visit account_path + first(:link, t('links.sign_out')).click + click_submit_default if sp == :saml # SAML SLO request + end + + it 'does not require the user to verify attributes' do + visit_idp_from_sp_with_loa3(sp) + click_link t('links.sign_in') + sign_in_user(user) + click_submit_default + + if sp == :oidc + expect(current_url).to include('http://localhost:7654/auth/result') + elsif sp == :saml + expect(current_url).to include(api_saml_auth_url) + end + end + end +end From 25fc1b8a2ee03f9a320089e73663a26dbb828931 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Fri, 13 Apr 2018 10:08:35 -0500 Subject: [PATCH 07/39] Break phone input tests into their own spec (#2095) **Why**: To draw a boundary between code that tests the behavior at the phone step from the code that tests the form --- spec/features/idv/phone_input_spec.rb | 23 +++++++++++++++++++++ spec/features/idv/phone_spec.rb | 26 ------------------------ spec/support/features/idv_step_helper.rb | 22 ++++++++++++++++++++ 3 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 spec/features/idv/phone_input_spec.rb create mode 100644 spec/support/features/idv_step_helper.rb diff --git a/spec/features/idv/phone_input_spec.rb b/spec/features/idv/phone_input_spec.rb new file mode 100644 index 00000000000..207cc297ad0 --- /dev/null +++ b/spec/features/idv/phone_input_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +feature 'IdV phone number input', :idv_job, :js do + include IdvStepHelper + + before do + complete_idv_steps_before_phone_step + end + + scenario 'phone input only allows numbers' do + fill_in 'Phone', with: '' + find('#idv_phone_form_phone').native.send_keys('abcd1234') + + expect(find('#idv_phone_form_phone').value).to eq '1 (234) ' + end + + scenario 'phone input does not format international numbers' do + fill_in 'Phone', with: '' + find('#idv_phone_form_phone').native.send_keys('+81543543643') + + expect(find('#idv_phone_form_phone').value).to eq '+1 (815) 435-4364' + end +end diff --git a/spec/features/idv/phone_spec.rb b/spec/features/idv/phone_spec.rb index d9151b7fe76..1268c35c925 100644 --- a/spec/features/idv/phone_spec.rb +++ b/spec/features/idv/phone_spec.rb @@ -99,32 +99,6 @@ end end - scenario 'phone field only allows numbers', js: true, idv_job: true do - sign_in_and_2fa_user - visit verify_session_path - fill_out_idv_form_ok - click_idv_continue - - visit verify_phone_path - fill_in 'Phone', with: '' - find('#idv_phone_form_phone').native.send_keys('abcd1234') - - expect(find('#idv_phone_form_phone').value).to eq '1 (234) ' - end - - scenario 'phone field does not format international numbers', :js, idv_job: true do - sign_in_and_2fa_user - visit verify_session_path - fill_out_idv_form_ok - click_idv_continue - - visit verify_phone_path - fill_in 'Phone', with: '' - find('#idv_phone_form_phone').native.send_keys('+81543543643') - - expect(find('#idv_phone_form_phone').value).to eq '+1 (815) 435-4364' - end - def complete_idv_profile_with_phone(phone) fill_out_idv_form_ok click_idv_continue diff --git a/spec/support/features/idv_step_helper.rb b/spec/support/features/idv_step_helper.rb new file mode 100644 index 00000000000..37fb80ff395 --- /dev/null +++ b/spec/support/features/idv_step_helper.rb @@ -0,0 +1,22 @@ +module IdvStepHelper + def self.included(base) + base.class_eval { include IdvHelper } + end + + def start_idv_at_profile_step(user = user_with_2fa) + sign_in_and_2fa_user(user) + visit verify_path unless current_path == verify_path + click_idv_begin + end + + def complete_idv_steps_before_address_step(user = user_with_2fa) + start_idv_at_profile_step(user) + fill_out_idv_form_ok + click_idv_continue + end + + def complete_idv_steps_before_phone_step(user = user_with_2fa) + complete_idv_steps_before_address_step(user) + click_idv_address_choose_phone + end +end From 2ae1b73d8056607e51bf26a89b852ea1b9224f59 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Fri, 13 Apr 2018 10:57:41 -0500 Subject: [PATCH 08/39] Use shared examples to test cancelation behavior (#2094) Use shared examples to test cancelation behavior **Why**: So that we can test that cancelation is consistent across IdV steps. Note that this tests the behavior with the different protocols because the difference between cancellation behavior with and without an SP is surprisingly different. Also, this commit adds some empty contexts for steps that don't have a cancel button with the assumption that we want to add a cancel button to those steps. --- spec/features/idv/cancel_idv_step_spec.rb | 41 +++++++++++++ spec/features/idv/flow_spec.rb | 57 ------------------ spec/features/idv/phone_input_spec.rb | 1 + spec/support/features/idv_helper.rb | 13 +++- spec/support/features/idv_step_helper.rb | 49 ++++++++++++++- .../features/javascript_driver_helper.rb | 5 ++ .../idv_examples/cancel_at_idv_step.rb | 60 +++++++++++++++++++ 7 files changed, 166 insertions(+), 60 deletions(-) create mode 100644 spec/features/idv/cancel_idv_step_spec.rb create mode 100644 spec/support/features/javascript_driver_helper.rb create mode 100644 spec/support/idv_examples/cancel_at_idv_step.rb diff --git a/spec/features/idv/cancel_idv_step_spec.rb b/spec/features/idv/cancel_idv_step_spec.rb new file mode 100644 index 00000000000..0523fac9faf --- /dev/null +++ b/spec/features/idv/cancel_idv_step_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +feature 'cancel at IdV step', :idv_job do + include IdvStepHelper + + context 'verify step' do + it_behaves_like 'cancel at idv step', :verify + it_behaves_like 'cancel at idv step', :verify, :oidc + it_behaves_like 'cancel at idv step', :verify, :saml + end + + context 'profile step' do + it_behaves_like 'cancel at idv step', :profile + it_behaves_like 'cancel at idv step', :profile, :oidc + it_behaves_like 'cancel at idv step', :profile, :saml + end + + context 'address step' do + it_behaves_like 'cancel at idv step', :address + it_behaves_like 'cancel at idv step', :address, :oidc + it_behaves_like 'cancel at idv step', :address, :saml + end + + xcontext 'phone step' do + # Phone step doesn't have a cancel button :( + end + + xcontext 'phone otp delivery method selection step' do + # Phone OTP delivery method step doesn't have a cancel button :( + end + + context 'phone otp verification step' do + it_behaves_like 'cancel at idv step', :phone_otp_verification + it_behaves_like 'cancel at idv step', :phone_otp_verification, :oidc + it_behaves_like 'cancel at idv step', :phone_otp_verification, :saml + end + + xcontext 'usps step' do + # USPS step does not have a cancel button :( + end +end diff --git a/spec/features/idv/flow_spec.rb b/spec/features/idv/flow_spec.rb index 8613c06f3a9..004ee2a276e 100644 --- a/spec/features/idv/flow_spec.rb +++ b/spec/features/idv/flow_spec.rb @@ -9,11 +9,6 @@ visit verify_path end - scenario 'decline to verify identity' do - click_link t('links.cancel') - expect(page).to have_content(t('idv.titles.cancel')) - end - scenario 'proceed to verify identity' do click_link 'Yes' @@ -227,58 +222,6 @@ end end - context 'cancel from USPS/Phone verification screen' do - context 'without js' do - it 'returns user to profile path' do - sign_in_and_2fa_user - loa3_sp_session - visit verify_session_path - - fill_out_idv_form_ok - click_idv_continue - - click_idv_cancel - - expect(current_path).to eq(account_path) - end - end - - context 'with js', js: true do - it 'redirects to profile from a modal' do - sign_in_and_2fa_user - loa3_sp_session - visit verify_session_path - - fill_out_idv_form_ok - click_idv_continue - - click_on t('links.cancel_idv') - click_idv_cancel_modal - - expect(current_path).to eq(account_path) - end - end - end - - scenario 'cancelling phone OTP verification redirects to verification cancel' do - allow(Figaro.env).to receive(:otp_delivery_blocklist_maxretry).and_return('4') - different_phone = '555-555-9876' - - sign_in_and_2fa_user - visit verify_session_path - - fill_out_idv_form_ok - click_idv_continue - click_idv_address_choose_phone - fill_out_phone_form_ok(different_phone) - click_idv_continue - choose_idv_otp_delivery_method_sms - - click_on t('links.cancel') - - expect(current_path).to eq verify_cancel_path - end - scenario 'attempting to skip OTP phone confirmation redirects to OTP confirmation', :js do different_phone = '555-555-9876' user = sign_in_live_with_2fa diff --git a/spec/features/idv/phone_input_spec.rb b/spec/features/idv/phone_input_spec.rb index 207cc297ad0..8deb53e9abc 100644 --- a/spec/features/idv/phone_input_spec.rb +++ b/spec/features/idv/phone_input_spec.rb @@ -4,6 +4,7 @@ include IdvStepHelper before do + start_idv_from_sp complete_idv_steps_before_phone_step end diff --git a/spec/support/features/idv_helper.rb b/spec/support/features/idv_helper.rb index b411fd71e17..76f5f59aa93 100644 --- a/spec/support/features/idv_helper.rb +++ b/spec/support/features/idv_helper.rb @@ -1,4 +1,8 @@ module IdvHelper + def self.included(base) + base.class_eval { include JavascriptDriverHelper } + end + def max_attempts_less_one Idv::Attempter.idv_max_attempts - 1 end @@ -127,7 +131,14 @@ def complete_idv_profile_ok(user, password = user_password) def visit_idp_from_sp_with_loa3(sp) if sp == :saml - @saml_authn_request = auth_request.create(loa3_with_bundle_saml_settings) + settings = loa3_with_bundle_saml_settings + settings.security[:embed_sign] = false + if javascript_enabled? + idp_domain_name = "#{page.server.host}:#{page.server.port}" + settings.idp_sso_target_url = "http://#{idp_domain_name}/api/saml/auth" + settings.idp_slo_target_url = "http://#{idp_domain_name}/api/saml/logout" + end + @saml_authn_request = auth_request.create(settings) visit @saml_authn_request elsif sp == :oidc @state = SecureRandom.hex diff --git a/spec/support/features/idv_step_helper.rb b/spec/support/features/idv_step_helper.rb index 37fb80ff395..34509b669fd 100644 --- a/spec/support/features/idv_step_helper.rb +++ b/spec/support/features/idv_step_helper.rb @@ -3,14 +3,27 @@ def self.included(base) base.class_eval { include IdvHelper } end - def start_idv_at_profile_step(user = user_with_2fa) + def start_idv_from_sp(sp = :oidc) + if sp.present? + visit_idp_from_sp_with_loa3(sp) + click_link t('links.sign_in') + else + visit root_path + end + end + + def complete_idv_steps_before_verify_step(user = user_with_2fa) sign_in_and_2fa_user(user) visit verify_path unless current_path == verify_path + end + + def complete_idv_steps_before_profile_step(user = user_with_2fa) + complete_idv_steps_before_verify_step(user) click_idv_begin end def complete_idv_steps_before_address_step(user = user_with_2fa) - start_idv_at_profile_step(user) + complete_idv_steps_before_profile_step(user) fill_out_idv_form_ok click_idv_continue end @@ -19,4 +32,36 @@ def complete_idv_steps_before_phone_step(user = user_with_2fa) complete_idv_steps_before_address_step(user) click_idv_address_choose_phone end + + def complete_idv_steps_before_usps_step(user = user_with_2fa) + complete_idv_steps_before_address_step(user) + click_idv_address_choose_usps + end + + def complete_idv_steps_before_phone_otp_delivery_selection_step(user = user_with_2fa) + complete_idv_steps_before_phone_step(user) + fill_out_phone_form_ok('2341230638') + click_idv_continue + end + + def complete_idv_steps_before_phone_otp_verification_step(user = user_with_2fa) + complete_idv_steps_before_phone_otp_delivery_selection_step(user) + choose_idv_otp_delivery_method_sms + end + + def complete_idv_steps_before_review_step(user = user_with_2fa) + complete_idv_steps_before_phone_step(user) + fill_out_phone_form_ok(user.phone) + click_idv_continue + end + + def complete_idv_steps_before_confirmation_step(user = user_with_2fa) + complete_idv_steps_before_review_step(user) + fill_in 'Password', with: password + click_continue + end + + def complete_idv_steps_before_step(step, user = user_with_2fa) + send("complete_idv_steps_before_#{step}_step", user) + end end diff --git a/spec/support/features/javascript_driver_helper.rb b/spec/support/features/javascript_driver_helper.rb new file mode 100644 index 00000000000..347c92399e2 --- /dev/null +++ b/spec/support/features/javascript_driver_helper.rb @@ -0,0 +1,5 @@ +module JavascriptDriverHelper + def javascript_enabled? + Capybara.current_driver == Capybara.javascript_driver + end +end diff --git a/spec/support/idv_examples/cancel_at_idv_step.rb b/spec/support/idv_examples/cancel_at_idv_step.rb new file mode 100644 index 00000000000..1f5548b374e --- /dev/null +++ b/spec/support/idv_examples/cancel_at_idv_step.rb @@ -0,0 +1,60 @@ +shared_examples 'cancel at idv step' do |step, sp| + include SamlAuthHelper + + before do + start_idv_from_sp(sp) + complete_idv_steps_before_step(step) + end + + context 'without js' do + it 'sends the user to the account page', if: sp.present? do + click_idv_cancel + expect(current_url).to eq(account_url) + end + + it 'shows the user a failure message with the option to go back to idv', if: sp.nil? do + click_link t('links.cancel') + + expect(page).to have_content(t('idv.titles.cancel')) + expect(page).to have_content(t('idv.messages.cancel', app: 'login.gov')) + expect(current_path).to eq(verify_cancel_path) + + click_link t('forms.buttons.back') + + expect(current_url).to eq(verify_url) + end + end + + context 'with js', :js do + it 'displays a modal with options to continue or return to account page', if: sp.present? do + # Clicking cancel displays the modal + click_on t('links.cancel_idv') + expect(page).to have_content(t('idv.cancel.modal_header')) + + # Clicking continue should hide the modal + click_on t('idv.buttons.continue') + expect(page).to_not have_content(t('idv.cancel.modal_header')) + + # Clicking cancel again reveals the modal + click_on t('links.cancel_idv') + expect(page).to have_content(t('idv.cancel.modal_header')) + + # Clicking return to account takes us to the account page + page.find_button(t('idv.buttons.cancel')).trigger('click') + expect(page).to have_content(t('headings.account.login_info')) + expect(current_path).to eq(account_path) + end + + it 'shows the user a failure message with the option to go back to idv', if: sp.nil? do + click_link t('links.cancel') + + expect(page).to have_content(t('idv.titles.cancel')) + expect(page).to have_content(t('idv.messages.cancel', app: 'login.gov')) + expect(current_path).to eq(verify_cancel_path) + + click_link t('forms.buttons.back') + + expect(current_path).to eq(verify_path) + end + end +end From 92375fcb79ca7d58b02a350b96f127c8094630d9 Mon Sep 17 00:00:00 2001 From: Tim Baxter <32077682+tbaxter-18f@users.noreply.github.com> Date: Fri, 13 Apr 2018 13:17:03 -0500 Subject: [PATCH 09/39] LG-141 Allow authenticator app setup during signup (#2061) LG-141 Allow authenticator app setup during signup **Why**: To provide more choices for users. --- .../users/totp_setup_controller.rb | 35 +++- app/models/user.rb | 2 +- app/services/analytics.rb | 1 + ...tp_delivery_preference_selection.html.slim | 2 + config/locales/links/en.yml | 1 + config/locales/links/es.yml | 1 + config/locales/links/fr.yml | 1 + .../users/totp_setup_controller_spec.rb | 193 ++++++++++++++---- spec/features/users/sign_up_spec.rb | 27 +++ spec/support/features/session_helper.rb | 25 ++- .../shared_examples/account_creation.rb | 22 ++ 11 files changed, 258 insertions(+), 52 deletions(-) diff --git a/app/controllers/users/totp_setup_controller.rb b/app/controllers/users/totp_setup_controller.rb index 13779ae0638..e8a4c829015 100644 --- a/app/controllers/users/totp_setup_controller.rb +++ b/app/controllers/users/totp_setup_controller.rb @@ -1,11 +1,13 @@ module Users class TotpSetupController < ApplicationController - before_action :confirm_two_factor_authenticated + before_action :authenticate_user! + before_action :confirm_two_factor_authenticated, if: :two_factor_enabled? def new return redirect_to account_url if current_user.totp_enabled? - user_session[:new_totp_secret] = current_user.generate_totp_secret if new_totp_secret.nil? + track_event + store_totp_secret_in_session @code = new_totp_secret @qrcode = current_user.decorate.qrcode(new_totp_secret) @@ -35,12 +37,39 @@ def disable private + def two_factor_enabled? + current_user.two_factor_enabled? + end + + def track_event + properties = { user_signed_up: current_user.two_factor_enabled? } + analytics.track_event(Analytics::TOTP_SETUP_VISIT, properties) + end + + def store_totp_secret_in_session + user_session[:new_totp_secret] = current_user.generate_totp_secret if new_totp_secret.nil? + end + def process_valid_code + mark_user_as_fully_authenticated flash[:success] = t('notices.totp_configured') - redirect_to account_url + redirect_to url_after_entering_valid_code user_session.delete(:new_totp_secret) end + def mark_user_as_fully_authenticated + user_session[TwoFactorAuthentication::NEED_AUTHENTICATION] = false + user_session[:authn_at] = Time.zone.now + end + + def url_after_entering_valid_code + if current_user.decorate.should_acknowledge_personal_key?(user_session) + sign_up_personal_key_url + else + account_url + end + end + def process_invalid_code flash[:error] = t('errors.invalid_totp') redirect_to authenticator_setup_url diff --git a/app/models/user.rb b/app/models/user.rb index 10342010e04..1143c609f5f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -57,7 +57,7 @@ def need_two_factor_authentication?(_request) end def two_factor_enabled? - phone.present? + phone.present? || totp_enabled? end def send_two_factor_authentication_code(_code) diff --git a/app/services/analytics.rb b/app/services/analytics.rb index 9e2a8ae9e06..fde1fd17b63 100644 --- a/app/services/analytics.rb +++ b/app/services/analytics.rb @@ -100,6 +100,7 @@ def browser SESSION_TIMED_OUT = 'Session Timed Out'.freeze SIGN_IN_PAGE_VISIT = 'Sign in page visited'.freeze TOTP_SETUP = 'TOTP Setup'.freeze + TOTP_SETUP_VISIT = 'TOTP Setup Visited'.freeze TOTP_USER_DISABLED = 'TOTP: User Disabled TOTP'.freeze TWILIO_PHONE_VALIDATION_FAILED = 'Twilio Phone Validation Failed'.freeze USER_REGISTRATION_AGENCY_HANDOFF_PAGE_VISIT = 'User registration: agency handoff visited'.freeze diff --git a/app/views/users/shared/_otp_delivery_preference_selection.html.slim b/app/views/users/shared/_otp_delivery_preference_selection.html.slim index e9bd13b2cc2..fa4c2f9e059 100644 --- a/app/views/users/shared/_otp_delivery_preference_selection.html.slim +++ b/app/views/users/shared/_otp_delivery_preference_selection.html.slim @@ -15,3 +15,5 @@ = t('devise.two_factor_authentication.otp_delivery_preference.voice') p#otp_delivery_preference_instruction.mt1.mb0 = t('devise.two_factor_authentication.otp_delivery_preference.instruction') + p.mb0 + = link_to t('links.two_factor_authentication.app_option'), authenticator_setup_path diff --git a/config/locales/links/en.yml b/config/locales/links/en.yml index 00bece1c1f1..22e23570c67 100644 --- a/config/locales/links/en.yml +++ b/config/locales/links/en.yml @@ -29,6 +29,7 @@ en: sign_in: Sign in sign_out: Sign out two_factor_authentication: + app_option: Use an authentication application instead. app: get a security code via authentication app resend_code: sms: Get another text message diff --git a/config/locales/links/es.yml b/config/locales/links/es.yml index 7144d66efe0..3e8298c9a9c 100644 --- a/config/locales/links/es.yml +++ b/config/locales/links/es.yml @@ -30,6 +30,7 @@ es: sign_in: Iniciar sesi贸n sign_out: Cerrar sesi贸n two_factor_authentication: + app_option: Use una aplicaci贸n de autenticaci贸n en su lugar. app: Obtener un c贸digo de seguridad mediante la app de autenticaci贸n resend_code: sms: Obtener otro mensaje de texto diff --git a/config/locales/links/fr.yml b/config/locales/links/fr.yml index 73113d90c7a..825433811b4 100644 --- a/config/locales/links/fr.yml +++ b/config/locales/links/fr.yml @@ -29,6 +29,7 @@ fr: sign_in: Connexion sign_out: D茅connexion two_factor_authentication: + app_option: Utilisez une application d'authentification 脿 la place. app: obtenir un code de s茅curit茅 par l'application d'authentification resend_code: sms: Recevoir un autre SMS diff --git a/spec/controllers/users/totp_setup_controller_spec.rb b/spec/controllers/users/totp_setup_controller_spec.rb index 36658c8c51a..e7139ca04b2 100644 --- a/spec/controllers/users/totp_setup_controller_spec.rb +++ b/spec/controllers/users/totp_setup_controller_spec.rb @@ -2,18 +2,22 @@ describe Users::TotpSetupController, devise: true do describe 'before_actions' do - it 'includes confirm_two_factor_authenticated' do + it 'includes appropriate before_actions' do expect(subject).to have_actions( :before, - :confirm_two_factor_authenticated + :authenticate_user!, + [:confirm_two_factor_authenticated, if: :two_factor_enabled?], ) end end describe '#new' do - context 'user has not yet enabled authenticator app' do + context 'user is setting up authenticator app after account creation' do before do - stub_sign_in + stub_analytics + user = build(:user, phone: '703-555-1212') + stub_sign_in(user) + allow(@analytics).to receive(:track_event) get :new end @@ -30,6 +34,13 @@ subject.current_user.decorate.qrcode(subject.user_session[:new_totp_secret]) ).not_to be_nil end + + it 'captures an analytics event' do + properties = { user_signed_up: true } + + expect(@analytics). + to have_received(:track_event).with(Analytics::TOTP_SETUP_VISIT, properties) + end end context 'user has already enabled authenticator app' do @@ -43,62 +54,154 @@ expect(response).to redirect_to account_path end end - end - describe '#confirm' do - context 'when user presents invalid code' do + context 'user is setting up authenticator app during account creation' do before do - stub_sign_in stub_analytics + stub_sign_in_before_2fa allow(@analytics).to receive(:track_event) - get :new - patch :confirm, params: { code: 123 } end - it 'redirects with an error message' do - expect(response).to redirect_to(authenticator_setup_path) - expect(flash[:error]).to eq t('errors.invalid_totp') - expect(subject.current_user.totp_enabled?).to be(false) + it 'returns a 200 status code' do + expect(response.status).to eq(200) + end - result = { - success: false, - errors: {}, - } - expect(@analytics).to have_received(:track_event).with(Analytics::TOTP_SETUP, result) + it 'sets new_totp_secret in user_session' do + expect(subject.user_session[:new_totp_secret]).not_to be_nil end - end - context 'when user presents correct code' do - before do - stub_sign_in - stub_analytics - allow(@analytics).to receive(:track_event) + it 'can be used to generate a qrcode with UserDecorator#qrcode' do + expect( + subject.current_user.decorate.qrcode(subject.user_session[:new_totp_secret]) + ).not_to be_nil + end + + it 'captures an analytics event' do + properties = { user_signed_up: false } + + expect(@analytics). + to have_received(:track_event).with(Analytics::TOTP_SETUP_VISIT, properties) + end + end + end - code = '123455' - totp_secret = 'abdef' - subject.user_session[:new_totp_secret] = totp_secret - form = instance_double(TotpSetupForm) + describe '#confirm' do + context 'user is already signed up' do + context 'when user presents invalid code' do + before do + user = build(:user, personal_key: 'ABCD-DEFG-HIJK-LMNO') + stub_sign_in(user) + stub_analytics + allow(@analytics).to receive(:track_event) + + get :new + patch :confirm, params: { code: 123 } + end + + it 'redirects with an error message' do + expect(response).to redirect_to(authenticator_setup_path) + expect(flash[:error]).to eq t('errors.invalid_totp') + expect(subject.current_user.totp_enabled?).to be(false) + + result = { + success: false, + errors: {}, + } + expect(@analytics).to have_received(:track_event).with(Analytics::TOTP_SETUP, result) + end + end - allow(TotpSetupForm).to receive(:new). - with(subject.current_user, totp_secret, code).and_return(form) - response = FormResponse.new(success: true, errors: {}) - allow(form).to receive(:submit).and_return(response) + context 'when user presents correct code' do + before do + user = build(:user, personal_key: 'ABCD-DEFG-HIJK-LMNO') + stub_sign_in(user) + stub_analytics + allow(@analytics).to receive(:track_event) + + code = '123455' + totp_secret = 'abdef' + subject.user_session[:new_totp_secret] = totp_secret + form = instance_double(TotpSetupForm) + + allow(TotpSetupForm).to receive(:new). + with(subject.current_user, totp_secret, code).and_return(form) + response = FormResponse.new(success: true, errors: {}) + allow(form).to receive(:submit).and_return(response) + + get :new + patch :confirm, params: { code: code } + end + + it 'redirects to account_path with a success message' do + expect(response).to redirect_to(account_path) + expect(flash[:success]).to eq t('notices.totp_configured') + expect(subject.user_session[:new_totp_secret]).to be_nil + + result = { + success: true, + errors: {}, + } + expect(@analytics).to have_received(:track_event).with(Analytics::TOTP_SETUP, result) + end + end + end - get :new - patch :confirm, params: { code: code } + context 'user is not yet signed up' do + context 'when user presents invalid code' do + before do + stub_sign_in_before_2fa + stub_analytics + allow(@analytics).to receive(:track_event) + + get :new + patch :confirm, params: { code: 123 } + end + + it 'redirects with an error message' do + expect(response).to redirect_to(authenticator_setup_path) + expect(flash[:error]).to eq t('errors.invalid_totp') + expect(subject.current_user.totp_enabled?).to be(false) + + result = { + success: false, + errors: {}, + } + expect(@analytics).to have_received(:track_event).with(Analytics::TOTP_SETUP, result) + end end - it 'redirects to account_path with a success message' do - expect(response).to redirect_to(account_path) - expect(flash[:success]).to eq t('notices.totp_configured') - expect(subject.user_session[:new_totp_secret]).to be_nil - - result = { - success: true, - errors: {}, - } - expect(@analytics).to have_received(:track_event).with(Analytics::TOTP_SETUP, result) + context 'when user presents correct code' do + before do + stub_sign_in_before_2fa + stub_analytics + allow(@analytics).to receive(:track_event) + + code = '123455' + totp_secret = 'abdef' + subject.user_session[:new_totp_secret] = totp_secret + form = instance_double(TotpSetupForm) + + allow(TotpSetupForm).to receive(:new). + with(subject.current_user, totp_secret, code).and_return(form) + response = FormResponse.new(success: true, errors: {}) + allow(form).to receive(:submit).and_return(response) + + get :new + patch :confirm, params: { code: code } + end + + it 'redirects to personal key page with a success message' do + expect(response).to redirect_to(sign_up_personal_key_url) + expect(flash[:success]).to eq t('notices.totp_configured') + expect(subject.user_session[:new_totp_secret]).to be_nil + + result = { + success: true, + errors: {}, + } + expect(@analytics).to have_received(:track_event).with(Analytics::TOTP_SETUP, result) + end end end end diff --git a/spec/features/users/sign_up_spec.rb b/spec/features/users/sign_up_spec.rb index 2086cd42958..0ae1aa993b0 100644 --- a/spec/features/users/sign_up_spec.rb +++ b/spec/features/users/sign_up_spec.rb @@ -148,4 +148,31 @@ it_behaves_like 'csrf error when acknowledging personal key', :oidc it_behaves_like 'creating an account with the site in Spanish', :saml it_behaves_like 'creating an account with the site in Spanish', :oidc + + it_behaves_like 'creating an account using authenticator app for 2FA', :saml + it_behaves_like 'creating an account using authenticator app for 2FA', :oidc + + it 'allows a user to choose TOTP as 2FA method during sign up' do + user = create(:user) + sign_in_user(user) + set_up_2fa_with_authenticator_app + click_acknowledge_personal_key + + expect(page).to have_current_path account_path + end + + it 'does not bypass 2FA when accessing authenticator_setup_path if the user is 2FA enabled' do + user = create(:user, :signed_up) + sign_in_user(user) + visit authenticator_setup_path + + expect(page).to have_current_path login_two_factor_path(otp_delivery_preference: 'sms', reauthn: false) + end + + it 'prompts to sign in when accessing authenticator_setup_path before signing in' do + user = create(:user, :signed_up) + visit authenticator_setup_path + + expect(page).to have_current_path root_path + end end diff --git a/spec/support/features/session_helper.rb b/spec/support/features/session_helper.rb index ef6a841d44c..e7babdcfb7e 100644 --- a/spec/support/features/session_helper.rb +++ b/spec/support/features/session_helper.rb @@ -351,14 +351,33 @@ def set_up_2fa_with_valid_phone end def register_user(email = 'test@test.com') + confirm_email_and_password(email) + set_up_2fa_with_valid_phone + click_submit_default + User.find_with_email(email) + end + + def confirm_email_and_password(email) allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true) click_link t('sign_up.registrations.create_account') submit_form_with_valid_email(email) click_confirmation_link_in_email(email) submit_form_with_valid_password - set_up_2fa_with_valid_phone - click_submit_default - User.find_with_email(email) + end + + def register_user_with_authenticator_app(email = 'test@test.com') + confirm_email_and_password(email) + set_up_2fa_with_authenticator_app + end + + def set_up_2fa_with_authenticator_app + click_link t('links.two_factor_authentication.app_option') + + expect(page).to have_current_path authenticator_setup_path + + secret = find('#qr-code').text + fill_in 'code', with: generate_totp_code(secret) + click_button 'Submit' end def sign_in_via_branded_page(user) diff --git a/spec/support/shared_examples/account_creation.rb b/spec/support/shared_examples/account_creation.rb index 5b3497c9d20..8c19d125035 100644 --- a/spec/support/shared_examples/account_creation.rb +++ b/spec/support/shared_examples/account_creation.rb @@ -46,3 +46,25 @@ end end end + +shared_examples 'creating an account using authenticator app for 2FA' do |sp| + it 'redirects to the SP', email: true do + visit_idp_from_sp_with_loa1(sp) + register_user_with_authenticator_app + click_acknowledge_personal_key + + if sp == :oidc + expect(page.response_headers['Content-Security-Policy']). + to(include('form-action \'self\' http://localhost:7654')) + end + + click_on t('forms.buttons.continue') + expect(current_url).to eq @saml_authn_request if sp == :saml + + if sp == :oidc + redirect_uri = URI(current_url) + + expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result') + end + end +end From 3223d294d45dbe7cd0a675cf56ce4a6d05363ba3 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Fri, 13 Apr 2018 14:30:37 -0500 Subject: [PATCH 10/39] Test max attempts at each step w/ shared examples (#2092) **Why**: So that we can test that the max attempt logic is consistent across steps. --- spec/features/idv/account_creation_spec.rb | 5 - spec/features/idv/max_attempts_spec.rb | 50 ++------ spec/support/features/idv_step_helper.rb | 6 +- spec/support/idv_examples/max_attempts.rb | 139 +++++++++++++-------- 4 files changed, 103 insertions(+), 97 deletions(-) diff --git a/spec/features/idv/account_creation_spec.rb b/spec/features/idv/account_creation_spec.rb index 938e8096194..1c9b5b72868 100644 --- a/spec/features/idv/account_creation_spec.rb +++ b/spec/features/idv/account_creation_spec.rb @@ -18,9 +18,4 @@ it_behaves_like 'idv state id data entry', :saml it_behaves_like 'idv state id data entry', :oidc end - - context 'retries limited by max step attempt limits' do - it_behaves_like 'idv max step attempts', :saml - it_behaves_like 'idv max step attempts', :oidc - end end diff --git a/spec/features/idv/max_attempts_spec.rb b/spec/features/idv/max_attempts_spec.rb index b34dffe7233..19cc4b2da08 100644 --- a/spec/features/idv/max_attempts_spec.rb +++ b/spec/features/idv/max_attempts_spec.rb @@ -1,44 +1,18 @@ require 'rails_helper' -feature 'IdV max attempts' do - include IdvHelper - - scenario 'profile shows failure modal after max attempts', :email, :idv_job, :js do - sign_in_and_2fa_user - visit verify_session_path - - max_attempts_less_one.times do - fill_out_idv_form_fail - click_continue - click_button t('idv.modal.button.warning') - - expect(current_path).to eq verify_session_result_path - end - - fill_out_idv_form_fail - click_continue - - expect(page).to have_css('.modal-fail', text: t('idv.modal.sessions.heading')) +feature 'IdV max attempts', :idv_job, :email do + include IdvStepHelper + include JavascriptDriverHelper + + context 'profile step' do + it_behaves_like 'verification step max attempts', :profile + it_behaves_like 'verification step max attempts', :profile, :oidc + it_behaves_like 'verification step max attempts', :profile, :saml end - scenario 'phone shows failure modal after max attempts', :email, :idv_job, :js do - sign_in_and_2fa_user - visit verify_session_path - fill_out_idv_form_ok - click_idv_continue - click_idv_address_choose_phone - - max_attempts_less_one.times do - fill_out_phone_form_fail - click_idv_continue - click_button t('idv.modal.button.warning') - - expect(current_path).to eq verify_phone_result_path - end - - fill_out_phone_form_fail - click_idv_continue - - expect(page).to have_css('.modal-fail', text: t('idv.modal.phone.heading')) + context 'phone step' do + it_behaves_like 'verification step max attempts', :phone + it_behaves_like 'verification step max attempts', :phone, :oidc + it_behaves_like 'verification step max attempts', :phone, :saml end end diff --git a/spec/support/features/idv_step_helper.rb b/spec/support/features/idv_step_helper.rb index 34509b669fd..d834db73ab9 100644 --- a/spec/support/features/idv_step_helper.rb +++ b/spec/support/features/idv_step_helper.rb @@ -1,6 +1,10 @@ module IdvStepHelper def self.included(base) - base.class_eval { include IdvHelper } + base.class_eval do + include IdvHelper + include JavascriptDriverHelper + include SamlAuthHelper + end end def start_idv_from_sp(sp = :oidc) diff --git a/spec/support/idv_examples/max_attempts.rb b/spec/support/idv_examples/max_attempts.rb index a17c48ad567..83d7b0a2742 100644 --- a/spec/support/idv_examples/max_attempts.rb +++ b/spec/support/idv_examples/max_attempts.rb @@ -1,78 +1,111 @@ -shared_examples 'idv max step attempts' do |sp| - it 'allows 3 attempts in 24 hours', :email do - visit_idp_from_sp_with_loa3(sp) - user = register_user - - max_attempts_less_one.times do - visit verify_session_path - fill_out_idv_form_fail - click_idv_continue +shared_examples 'verification step max attempts' do |step, sp| + let(:user) { user_with_2fa } + let(:step_locale_key) do + return :sessions if step == :profile + step + end - expect(current_path).to eq verify_session_result_path + before do + start_idv_from_sp(sp) + complete_idv_steps_before_step(step, user) + if step == :profile + perfom_maximum_allowed_idv_step_attempts { fill_out_idv_form_fail } + elsif step == :phone + perfom_maximum_allowed_idv_step_attempts { fill_out_phone_form_fail } end + end - user.reload - expect(user.idv_attempted_at).to_not be_nil - - fill_out_idv_form_fail - click_idv_continue - - expect(page).to have_css('.alert-error', text: t('idv.modal.sessions.heading')) + scenario 'more than 3 attempts in 24 hours prevents further attempts' do + # Blocked if visiting verify directly + visit verify_url + advance_to_phone_step if step == :phone + expect_user_to_be_unable_to_perform_idv(sp) - visit_idp_from_sp_with_loa3(sp) - expect(page).to have_content( - t('idv.messages.hardfail', hours: Figaro.env.idv_attempt_window_in_hours) - ) - expect(current_url).to eq verify_fail_url + # Blocked if visiting from an SP + visit_idp_from_sp_with_loa3(:oidc) + advance_to_phone_step if step == :phone + expect_user_to_be_unable_to_perform_idv(sp) - visit verify_session_path - expect(page).to have_content(t('idv.errors.hardfail')) - expect(current_url).to eq verify_fail_url + if step == :sessions + user.reload - user.reload - expect(user.idv_attempted_at).to_not be_nil + expect(user.idv_attempted_at).to_not be_nil + end end - scenario 'profile shows failure flash message after max attempts', :email do - visit_idp_from_sp_with_loa3(sp) - register_user + scenario 'after 24 hours the user can retry and complete idv' do + visit account_path + first(:link, t('links.sign_out')).click + reattempt_interval = (Figaro.env.idv_attempt_window_in_hours.to_i + 1).hours - click_idv_begin + Timecop.travel reattempt_interval do + visit_idp_from_sp_with_loa3(:oidc) + click_link t('links.sign_in') + sign_in_live_with_2fa(user) - max_attempts_less_one.times do - fill_out_idv_form_fail + expect(page).to_not have_content(t("idv.modal.#{step_locale_key}.heading")) + expect(current_url).to eq(verify_url) + + click_idv_begin + complete_idv_profile_ok(user) + click_acknowledge_personal_key click_idv_continue - expect(current_path).to eq verify_session_result_path + expect(current_url).to start_with('http://localhost:7654/auth/result') end - - fill_out_idv_form_fail - click_idv_continue - - expect(page).to have_css('.alert-error', text: t('idv.modal.sessions.heading')) - expect(current_path).to eq verify_session_result_path end - scenario 'phone shows failure flash after max attempts', :email do - visit_idp_from_sp_with_loa3(sp) - register_user + scenario 'user sees failure flash message' do + expect(page).to have_css('.alert-error', text: t("idv.modal.#{step_locale_key}.heading")) + expect(page).to have_css( + '.alert-error', + text: strip_tags(t("idv.modal.#{step_locale_key}.fail")) + ) + end - click_idv_begin - fill_out_idv_form_ok - click_idv_continue - click_idv_address_choose_phone + context 'with js', :js do + scenario 'user sees the failure modal' do + expect(page).to have_css('.modal-fail', text: t("idv.modal.#{step_locale_key}.heading")) + expect(page).to have_css( + '.modal-fail', + text: strip_tags(t("idv.modal.#{step_locale_key}.fail")) + ) + end + end + def perfom_maximum_allowed_idv_step_attempts max_attempts_less_one.times do - fill_out_phone_form_fail + yield click_idv_continue + click_button t('idv.modal.button.warning') if javascript_enabled? + end + yield + click_idv_continue + end - expect(current_path).to eq verify_phone_result_path + def expect_user_to_be_unable_to_perform_idv(sp) + expect(page).to have_content(t('idv.titles.hardfail', app: 'login.gov')) + if sp.present? + expect(page).to have_content( + t('idv.messages.hardfail', hours: Figaro.env.idv_attempt_window_in_hours) + ) + expect(page).to have_content( + strip_tags(t('idv.messages.hardfail4_html', sp: 'Test SP')) + ) + else + expect(page).to have_content( + strip_tags(t('idv.messages.help_center_html')) + ) end + expect(current_url).to eq(verify_fail_url) + end - fill_out_phone_form_fail - click_idv_continue + def advance_to_phone_step + click_idv_begin + click_idv_address_choose_phone + end - expect(page).to have_css('.alert-error', text: t('idv.modal.phone.heading')) - expect(current_path).to eq verify_phone_result_path + def strip_tags(*args) + ActionController::Base.helpers.strip_tags(*args) end end From 8c7cfd94a9727944af85aff9f8050a2f60ab2a1d Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Fri, 13 Apr 2018 14:50:12 -0500 Subject: [PATCH 11/39] Test failed and timed out jobs in shared examples (#2097) **Why**: To make sure that job failed and timeout behavior is consistent across steps, and to provide coverage for timeout behavior which was not covered before. --- spec/features/idv/failed_job_spec.rb | 28 ++---- spec/rails_helper.rb | 1 + spec/support/features/step_tags_helper.rb | 7 ++ spec/support/idv_examples/failed_idv_job.rb | 102 ++++++++++++++++++++ spec/support/idv_examples/max_attempts.rb | 4 - 5 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 spec/support/features/step_tags_helper.rb create mode 100644 spec/support/idv_examples/failed_idv_job.rb diff --git a/spec/features/idv/failed_job_spec.rb b/spec/features/idv/failed_job_spec.rb index f2d6a76b015..54bafd1306e 100644 --- a/spec/features/idv/failed_job_spec.rb +++ b/spec/features/idv/failed_job_spec.rb @@ -1,25 +1,15 @@ require 'rails_helper' -feature 'IdV session' do - include IdvHelper +feature 'IdV session', :idv_job do + include IdvStepHelper - context 'Idv job raises an error', idv_job: true do - it 'displays a warning that something went wrong' do - sign_in_and_2fa_user - - step = instance_double( - Idv::ProfileStep, attempts_exceeded?: false, vendor_validator_job_failed?: true - ) - allow(Idv::ProfileStep).to receive(:new).and_return(step) - allow(step).to receive(:submit). - and_return(FormResponse.new(success: false, errors: {}, extra: {})) - - visit verify_session_path - fill_out_idv_form_ok - click_idv_continue + context 'profile job' do + let(:idv_job_class) { Idv::ProfileJob } + it_behaves_like 'failed idv job', :profile + end - expect(page).to have_current_path(verify_session_result_path) - expect(page).to have_content t('idv.modal.sessions.jobfail') - end + context 'phone job' do + let(:idv_job_class) { Idv::PhoneJob } + it_behaves_like 'failed idv job', :phone end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 555968d4751..13e7c12f2c6 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -29,6 +29,7 @@ config.include Features::LocalizationHelper, type: :feature config.include Features::MailerHelper, type: :feature config.include Features::SessionHelper, type: :feature + config.include Features::StripTagsHelper, type: :feature config.include AnalyticsHelper config.include AwsKmsClientHelper config.include KeyRotationHelper diff --git a/spec/support/features/step_tags_helper.rb b/spec/support/features/step_tags_helper.rb new file mode 100644 index 00000000000..8131446fd35 --- /dev/null +++ b/spec/support/features/step_tags_helper.rb @@ -0,0 +1,7 @@ +module Features + module StripTagsHelper + def strip_tags(*args) + ActionController::Base.helpers.strip_tags(*args) + end + end +end diff --git a/spec/support/idv_examples/failed_idv_job.rb b/spec/support/idv_examples/failed_idv_job.rb new file mode 100644 index 00000000000..98f0ce8f837 --- /dev/null +++ b/spec/support/idv_examples/failed_idv_job.rb @@ -0,0 +1,102 @@ +shared_examples 'failed idv job' do |step| + let(:step_locale_key) do + return :sessions if step == :profile + step + end + + before do + visit_idp_from_sp_with_loa3(:oidc) + click_link t('links.sign_in') + complete_idv_steps_before_step(step) + end + + context 'the job raises an error' do + before do + stub_idv_job_to_raise_error_in_background(idv_job_class) + + fill_out_idv_form_ok if step == :profile + fill_out_phone_form_ok if step == :phone + click_idv_continue + end + + context 'without js' do + it 'shows a warning' do + expect(page).to have_content t("idv.modal.#{step_locale_key}.heading") + expect(page).to have_content t("idv.modal.#{step_locale_key}.jobfail") + expect(page).to have_current_path(verify_session_result_path) if step == :profile + expect(page).to have_current_path(verify_phone_result_path) if step == :phone + end + end + + context 'with js', :js do + it 'shows a modal' do + expect(page).to have_css('.modal-warning', text: t("idv.modal.#{step_locale_key}.heading")) + expect(page).to have_css( + '.modal-warning', + text: strip_tags(t("idv.modal.#{step_locale_key}.jobfail")) + ) + expect(page).to have_current_path(verify_session_result_path) if step == :profile + expect(page).to have_current_path(verify_phone_result_path) if step == :phone + end + end + end + + context 'the job times out' do + before do + stub_idv_job_to_timeout_in_background(idv_job_class) + + fill_out_idv_form_ok if step == :profile + fill_out_phone_form_ok('5202691958') if step == :phone + click_idv_continue + + Timecop.travel (Figaro.env.async_job_refresh_max_wait_seconds.to_i + 1).seconds + + visit current_path + end + + after do + Timecop.return + end + + context 'without js' do + it 'shows a warning' do + expect(page).to have_content t("idv.modal.#{step_locale_key}.heading") + expect(page).to have_content t("idv.modal.#{step_locale_key}.timeout") + expect(page).to have_current_path(verify_session_result_path) if step == :profile + expect(page).to have_current_path(verify_phone_result_path) if step == :phone + end + end + + context 'with js' do + it 'shows a modal' do + expect(page).to have_css('.modal-warning', text: t("idv.modal.#{step_locale_key}.heading")) + expect(page).to have_css( + '.modal-warning', + text: strip_tags(t("idv.modal.#{step_locale_key}.timeout")) + ) + expect(page).to have_current_path(verify_session_result_path) if step == :profile + expect(page).to have_current_path(verify_phone_result_path) if step == :phone + end + end + end + + def stub_idv_job_to_raise_error_in_background(idv_job_class) + allow(idv_job_class).to receive(:new).and_wrap_original do |new, *args| + idv_job = new.call(*args) + allow(idv_job).to receive(:verify_identity_with_vendor). + and_raise('this is a test error') + idv_job + end + allow(idv_job_class).to receive(:perform_now).and_wrap_original do |perform_now, *args| + begin + perform_now.call(*args) + rescue StandardError => err + # Swallow the error so it does not get re-raised by the job + end + end + end + + def stub_idv_job_to_timeout_in_background(idv_job_class) + allow(idv_job_class).to receive(:perform_now) + end +end diff --git a/spec/support/idv_examples/max_attempts.rb b/spec/support/idv_examples/max_attempts.rb index 83d7b0a2742..92995bbab8d 100644 --- a/spec/support/idv_examples/max_attempts.rb +++ b/spec/support/idv_examples/max_attempts.rb @@ -104,8 +104,4 @@ def advance_to_phone_step click_idv_begin click_idv_address_choose_phone end - - def strip_tags(*args) - ActionController::Base.helpers.strip_tags(*args) - end end From 720351852ab1d1813d191a658f9256485b4e93e5 Mon Sep 17 00:00:00 2001 From: David Corwin Date: Fri, 13 Apr 2018 21:58:10 -0700 Subject: [PATCH 12/39] Add a make task and Procfile for running IdP in development **WHY**: Because running it via rackup was not allowing connections on localhost:3000 from non-ruby/curl sources. For example, the `identity-oidc-phoenix` example SP could not access the IdP running in development. --- Makefile | 3 +++ Procfile_dev | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 Procfile_dev diff --git a/Makefile b/Makefile index 9e5e1b6fd2b..62a58d69ec6 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,9 @@ fast_test: run: foreman start -p $(PORT) +run-dev: + foreman start -p $(PORT) -f Procfile_dev + load_test: $(CONFIG) bin/load_test $(type) diff --git a/Procfile_dev b/Procfile_dev new file mode 100644 index 00000000000..304aa900a6a --- /dev/null +++ b/Procfile_dev @@ -0,0 +1,3 @@ +web: bin/rails s -b 127.0.0.1 -p ${PORT:-3000} +worker: bundle exec sidekiq --config config/sidekiq.yml +mailcatcher: mailcatcher -f From 4ac69c1481b4dc9e4e29ba82aa39c71e93f596b9 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Mon, 16 Apr 2018 09:51:35 -0500 Subject: [PATCH 13/39] Create a phone step spec (#2103) **Why**: So we can test all of the phone step features in one place --- spec/features/idv/cancel_idv_step_spec.rb | 4 - spec/features/idv/failed_job_spec.rb | 6 - spec/features/idv/max_attempts_spec.rb | 6 - spec/features/idv/steps/phone_step_spec.rb | 94 ++++++++++++++ spec/support/idv_examples/fail_to_verify.rb | 132 ++++++++++++++++++++ spec/support/idv_examples/failed_idv_job.rb | 4 + 6 files changed, 230 insertions(+), 16 deletions(-) create mode 100644 spec/features/idv/steps/phone_step_spec.rb create mode 100644 spec/support/idv_examples/fail_to_verify.rb diff --git a/spec/features/idv/cancel_idv_step_spec.rb b/spec/features/idv/cancel_idv_step_spec.rb index 0523fac9faf..2086f20dd75 100644 --- a/spec/features/idv/cancel_idv_step_spec.rb +++ b/spec/features/idv/cancel_idv_step_spec.rb @@ -21,10 +21,6 @@ it_behaves_like 'cancel at idv step', :address, :saml end - xcontext 'phone step' do - # Phone step doesn't have a cancel button :( - end - xcontext 'phone otp delivery method selection step' do # Phone OTP delivery method step doesn't have a cancel button :( end diff --git a/spec/features/idv/failed_job_spec.rb b/spec/features/idv/failed_job_spec.rb index 54bafd1306e..4f4f8c6b594 100644 --- a/spec/features/idv/failed_job_spec.rb +++ b/spec/features/idv/failed_job_spec.rb @@ -4,12 +4,6 @@ include IdvStepHelper context 'profile job' do - let(:idv_job_class) { Idv::ProfileJob } it_behaves_like 'failed idv job', :profile end - - context 'phone job' do - let(:idv_job_class) { Idv::PhoneJob } - it_behaves_like 'failed idv job', :phone - end end diff --git a/spec/features/idv/max_attempts_spec.rb b/spec/features/idv/max_attempts_spec.rb index 19cc4b2da08..c9586947b79 100644 --- a/spec/features/idv/max_attempts_spec.rb +++ b/spec/features/idv/max_attempts_spec.rb @@ -9,10 +9,4 @@ it_behaves_like 'verification step max attempts', :profile, :oidc it_behaves_like 'verification step max attempts', :profile, :saml end - - context 'phone step' do - it_behaves_like 'verification step max attempts', :phone - it_behaves_like 'verification step max attempts', :phone, :oidc - it_behaves_like 'verification step max attempts', :phone, :saml - end end diff --git a/spec/features/idv/steps/phone_step_spec.rb b/spec/features/idv/steps/phone_step_spec.rb new file mode 100644 index 00000000000..eec7853c11b --- /dev/null +++ b/spec/features/idv/steps/phone_step_spec.rb @@ -0,0 +1,94 @@ +require 'rails_helper' + +feature 'idv profile step', :idv_job do + include IdvStepHelper + + context 'with valid information' do + it 'allows the user to continue to the phone otp delivery selection step' do + start_idv_from_sp + complete_idv_steps_before_phone_step + fill_out_phone_form_ok + click_idv_continue + + expect(page).to have_content(t('idv.titles.otp_delivery_method')) + expect(page).to have_current_path(verify_otp_delivery_method_path) + end + end + + context 'after submitting valid information' do + it 'is re-entrant before confirming OTP' do + first_phone_number = '5551231234' + second_phone_number = '5557897890' + + start_idv_from_sp + complete_idv_steps_before_phone_step + fill_out_phone_form_ok(first_phone_number) + click_idv_continue + choose_idv_otp_delivery_method_sms + + expect(page).to have_content(first_phone_number) + + click_link t('forms.two_factor.try_again') + + expect(page).to have_content(t('idv.titles.session.phone')) + expect(page).to have_current_path(verify_phone_path) + + fill_out_phone_form_ok(second_phone_number) + click_idv_continue + choose_idv_otp_delivery_method_sms + + expect(page).to have_content(second_phone_number) + end + + it 'is not re-entrant after confirming OTP' do + user = user_with_2fa + + start_idv_from_sp + complete_idv_steps_before_phone_step(user) + fill_out_phone_form_ok + click_idv_continue + choose_idv_otp_delivery_method_sms + enter_correct_otp_code_for_user(user) + + visit verify_phone_path + expect(page).to have_content(t('idv.titles.session.review')) + expect(page).to have_current_path(verify_review_path) + + fill_in 'Password', with: user_password + click_continue + + # Currently this byasses the confirmation step since that is only + # accessible once + visit verify_phone_path + expect(page).to_not have_current_path(verify_phone_path) + end + end + + it 'does not allow the user to advance without completing' do + start_idv_from_sp + complete_idv_steps_before_phone_step + + # Try to skip ahead to review step + visit verify_review_path + # Get redirected to the address step (which leads to phone step) + expect(page).to have_current_path(verify_address_path) + end + + xcontext 'cancelling IdV' do + # The phone step does not have any cancel behavior :( + end + + context "when the user's information cannot be verified" do + it_behaves_like 'fail to verify idv info', :phone + end + + context 'when the IdV background job fails' do + it_behaves_like 'failed idv job', :phone + end + + context 'after the max number of attempts' do + it_behaves_like 'verification step max attempts', :phone + it_behaves_like 'verification step max attempts', :phone, :oidc + it_behaves_like 'verification step max attempts', :phone, :saml + end +end diff --git a/spec/support/idv_examples/fail_to_verify.rb b/spec/support/idv_examples/fail_to_verify.rb new file mode 100644 index 00000000000..301abe59bae --- /dev/null +++ b/spec/support/idv_examples/fail_to_verify.rb @@ -0,0 +1,132 @@ +shared_examples 'fail to verify idv info' do |step| + let(:step_locale_key) do + return :sessions if step == :profile + step + end + + before do + start_idv_from_sp + complete_idv_steps_before_step(step) + fill_out_idv_form_fail if step == :profile + fill_out_phone_form_fail if step == :phone + click_continue + end + + context 'without js' do + it 'renders a flash message and lets the user try again' do + expect_page_to_have_warning_message + expect(page).to have_current_path(verify_session_result_path) if step == :profile + expect(page).to have_current_path(verify_phone_result_path) if step == :phone + + fill_out_idv_form_ok if step == :profile + fill_out_phone_form_ok if step == :phone + click_idv_continue + + expect(page).to have_content(t('idv.titles.select_verification')) if step == :profile + expect(page).to have_current_path(verify_address_path) if step == :profile + expect(page).to have_content(t('idv.titles.otp_delivery_method')) if step == :phone + expect(page).to have_current_path(verify_otp_delivery_method_path) if step == :phone + end + end + + context 'with js', :js do + it 'renders a modal and lets the user try again' do + expect_page_to_have_warning_modal + expect(page).to have_current_path(verify_session_result_path) if step == :profile + expect(page).to have_current_path(verify_phone_result_path) if step == :phone + + dismiss_warning_modal + fill_out_idv_form_ok if step == :profile + fill_out_phone_form_ok if step == :phone + click_idv_continue + + expect(page).to have_content(t('idv.titles.select_verification')) if step == :profile + expect(page).to have_current_path(verify_address_path) if step == :profile + expect(page).to have_content(t('idv.titles.otp_delivery_method')) if step == :phone + expect(page).to have_current_path(verify_otp_delivery_method_path) if step == :phone + end + end + + def expect_page_to_have_warning_message + expect(page).to have_content t("idv.modal.#{step_locale_key}.heading") + expect(page).to have_content t("idv.modal.#{step_locale_key}.warning") + end + + def expect_page_to_have_warning_modal + expect(page).to have_css('.modal-warning', text: t("idv.modal.#{step_locale_key}.heading")) + expect(page).to have_css( + '.modal-warning', + text: strip_tags(t("idv.modal.#{step_locale_key}.warning")) + ) + end + + def dismiss_warning_modal + click_button t('idv.modal.button.warning') + end +end +shared_examples 'fail to verify idv info' do |step| + let(:step_locale_key) do + return :sessions if step == :profile + step + end + + before do + start_idv_from_sp + complete_idv_steps_before_step(step) + fill_out_idv_form_fail if step == :profile + fill_out_phone_form_fail if step == :phone + click_continue + end + + context 'without js' do + it 'renders a flash message and lets the user try again' do + expect_page_to_have_warning_message + expect(page).to have_current_path(verify_session_result_path) if step == :profile + expect(page).to have_current_path(verify_phone_result_path) if step == :phone + + fill_out_idv_form_ok if step == :profile + fill_out_phone_form_ok if step == :phone + click_idv_continue + + expect(page).to have_content(t('idv.titles.select_verification')) if step == :profile + expect(page).to have_current_path(verify_address_path) if step == :profile + expect(page).to have_content(t('idv.titles.otp_delivery_method')) if step == :phone + expect(page).to have_current_path(verify_otp_delivery_method_path) if step == :phone + end + end + + context 'with js', :js do + it 'renders a modal and lets the user try again' do + expect_page_to_have_warning_modal + expect(page).to have_current_path(verify_session_result_path) if step == :profile + expect(page).to have_current_path(verify_phone_result_path) if step == :phone + + dismiss_warning_modal + fill_out_idv_form_ok if step == :profile + fill_out_phone_form_ok if step == :phone + click_idv_continue + + expect(page).to have_content(t('idv.titles.select_verification')) if step == :profile + expect(page).to have_current_path(verify_address_path) if step == :profile + expect(page).to have_content(t('idv.titles.otp_delivery_method')) if step == :phone + expect(page).to have_current_path(verify_otp_delivery_method_path) if step == :phone + end + end + + def expect_page_to_have_warning_message + expect(page).to have_content t("idv.modal.#{step_locale_key}.heading") + expect(page).to have_content t("idv.modal.#{step_locale_key}.warning") + end + + def expect_page_to_have_warning_modal + expect(page).to have_css('.modal-warning', text: t("idv.modal.#{step_locale_key}.heading")) + expect(page).to have_css( + '.modal-warning', + text: strip_tags(t("idv.modal.#{step_locale_key}.warning")) + ) + end + + def dismiss_warning_modal + click_button t('idv.modal.button.warning') + end +end diff --git a/spec/support/idv_examples/failed_idv_job.rb b/spec/support/idv_examples/failed_idv_job.rb index 98f0ce8f837..dae5c935c0c 100644 --- a/spec/support/idv_examples/failed_idv_job.rb +++ b/spec/support/idv_examples/failed_idv_job.rb @@ -1,4 +1,8 @@ shared_examples 'failed idv job' do |step| + let(:idv_job_class) do + return Idv::ProfileJob if step == :profile + return Idv::PhoneJob if step == :phone + end let(:step_locale_key) do return :sessions if step == :profile step From 82776828e8dfe947e6fcc68b2f69879f725d85db Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Mon, 16 Apr 2018 11:10:19 -0500 Subject: [PATCH 14/39] Add address and OTP delivery method step specs (#2104) **Why**: So we can have the tests for the OTP delivery method and address step features in one place. --- spec/features/idv/cancel_idv_step_spec.rb | 10 --- spec/features/idv/phone_spec.rb | 22 ------- spec/features/idv/steps/address_step_spec.rb | 33 ++++++++++ .../phone_otp_delivery_selection_step_spec.rb | 58 ++++++++++++++++ spec/support/idv_examples/fail_to_verify.rb | 66 ------------------- 5 files changed, 91 insertions(+), 98 deletions(-) create mode 100644 spec/features/idv/steps/address_step_spec.rb create mode 100644 spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb diff --git a/spec/features/idv/cancel_idv_step_spec.rb b/spec/features/idv/cancel_idv_step_spec.rb index 2086f20dd75..111caa801cd 100644 --- a/spec/features/idv/cancel_idv_step_spec.rb +++ b/spec/features/idv/cancel_idv_step_spec.rb @@ -15,16 +15,6 @@ it_behaves_like 'cancel at idv step', :profile, :saml end - context 'address step' do - it_behaves_like 'cancel at idv step', :address - it_behaves_like 'cancel at idv step', :address, :oidc - it_behaves_like 'cancel at idv step', :address, :saml - end - - xcontext 'phone otp delivery method selection step' do - # Phone OTP delivery method step doesn't have a cancel button :( - end - context 'phone otp verification step' do it_behaves_like 'cancel at idv step', :phone_otp_verification it_behaves_like 'cancel at idv step', :phone_otp_verification, :oidc diff --git a/spec/features/idv/phone_spec.rb b/spec/features/idv/phone_spec.rb index 1268c35c925..d34d09dd263 100644 --- a/spec/features/idv/phone_spec.rb +++ b/spec/features/idv/phone_spec.rb @@ -27,28 +27,6 @@ expect(current_path).to eq account_path end - scenario 'phone number with no voice otp support only allows sms delivery' do - unsupported_phone = '242-555-5000' - user = create( - :user, :signed_up, - otp_delivery_preference: 'voice', - password: Features::SessionHelper::VALID_PASSWORD - ) - - sign_in_and_2fa_user(user) - visit verify_session_path - - allow(VoiceOtpSenderJob).to receive(:perform_later) - allow(SmsOtpSenderJob).to receive(:perform_later) - - complete_idv_profile_with_phone(unsupported_phone) - - expect(current_path).to eq login_two_factor_path(otp_delivery_preference: :sms) - expect(VoiceOtpSenderJob).to_not have_received(:perform_later) - expect(SmsOtpSenderJob).to have_received(:perform_later) - expect(page).to_not have_content(t('links.two_factor_authentication.resend_code.phone')) - end - scenario 'user cannot re-enter phone step and change phone after confirmation', :idv_job do user = sign_in_and_2fa_user diff --git a/spec/features/idv/steps/address_step_spec.rb b/spec/features/idv/steps/address_step_spec.rb new file mode 100644 index 00000000000..6bef86fc472 --- /dev/null +++ b/spec/features/idv/steps/address_step_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +feature 'IdV address step', :idv_job do + include IdvStepHelper + + context 'the user selects phone' do + it 'redirects them to the phone step' do + start_idv_from_sp + complete_idv_steps_before_address_step + click_idv_address_choose_phone + + expect(page).to have_content(t('idv.titles.session.phone')) + expect(page).to have_current_path(verify_phone_path) + end + end + + context 'the user selects usps' do + it 'redirects them to the usps step' do + start_idv_from_sp + complete_idv_steps_before_address_step + click_idv_address_choose_usps + + expect(page).to have_content(t('idv.titles.mail.verify')) + expect(page).to have_current_path(verify_usps_path) + end + end + + context 'cancelling IdV' do + it_behaves_like 'cancel at idv step', :address + it_behaves_like 'cancel at idv step', :address, :oidc + it_behaves_like 'cancel at idv step', :address, :saml + end +end diff --git a/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb b/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb new file mode 100644 index 00000000000..fb2597d6764 --- /dev/null +++ b/spec/features/idv/steps/phone_otp_delivery_selection_step_spec.rb @@ -0,0 +1,58 @@ +require 'rails_helper' + +feature 'IdV phone OTP deleivery method selection', :idv_job do + include IdvStepHelper + + context 'the users chooses sms' do + it 'sends an sms and redirects to otp verification' do + expect(VoiceOtpSenderJob).to_not receive(:perform_later) + expect(SmsOtpSenderJob).to receive(:perform_later) + + start_idv_from_sp + complete_idv_steps_before_phone_otp_delivery_selection_step + choose_idv_otp_delivery_method_sms + + expect(page).to have_content(t('devise.two_factor_authentication.header_text')) + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + end + end + + context 'the user chooses voice' do + it 'sends a voice call and redirects to otp verification' do + expect(VoiceOtpSenderJob).to receive(:perform_later) + expect(SmsOtpSenderJob).to_not receive(:perform_later) + + start_idv_from_sp + complete_idv_steps_before_phone_otp_delivery_selection_step + choose_idv_otp_delivery_method_voice + + expect(page).to have_content(t('devise.two_factor_authentication.header_text')) + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :voice)) + end + end + + context 'with a voice unsupported number' do + let(:unsupported_phone) { '242-555-5000' } + + before do + start_idv_from_sp + complete_idv_steps_before_phone_step + fill_out_phone_form_ok(unsupported_phone) + click_idv_continue + end + + it 'sends a sms even if the user chooses voice' do + expect(VoiceOtpSenderJob).to_not receive(:perform_later) + expect(SmsOtpSenderJob).to receive(:perform_later) + + choose_idv_otp_delivery_method_voice + + expect(page).to_not have_content(t('links.two_factor_authentication.resend_code.phone')) + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + end + end + + context 'cancelling IdV' do + # Phone OTP delivery method step doesn't have a cancel button :( + end +end diff --git a/spec/support/idv_examples/fail_to_verify.rb b/spec/support/idv_examples/fail_to_verify.rb index 301abe59bae..f15c4527529 100644 --- a/spec/support/idv_examples/fail_to_verify.rb +++ b/spec/support/idv_examples/fail_to_verify.rb @@ -64,69 +64,3 @@ def dismiss_warning_modal click_button t('idv.modal.button.warning') end end -shared_examples 'fail to verify idv info' do |step| - let(:step_locale_key) do - return :sessions if step == :profile - step - end - - before do - start_idv_from_sp - complete_idv_steps_before_step(step) - fill_out_idv_form_fail if step == :profile - fill_out_phone_form_fail if step == :phone - click_continue - end - - context 'without js' do - it 'renders a flash message and lets the user try again' do - expect_page_to_have_warning_message - expect(page).to have_current_path(verify_session_result_path) if step == :profile - expect(page).to have_current_path(verify_phone_result_path) if step == :phone - - fill_out_idv_form_ok if step == :profile - fill_out_phone_form_ok if step == :phone - click_idv_continue - - expect(page).to have_content(t('idv.titles.select_verification')) if step == :profile - expect(page).to have_current_path(verify_address_path) if step == :profile - expect(page).to have_content(t('idv.titles.otp_delivery_method')) if step == :phone - expect(page).to have_current_path(verify_otp_delivery_method_path) if step == :phone - end - end - - context 'with js', :js do - it 'renders a modal and lets the user try again' do - expect_page_to_have_warning_modal - expect(page).to have_current_path(verify_session_result_path) if step == :profile - expect(page).to have_current_path(verify_phone_result_path) if step == :phone - - dismiss_warning_modal - fill_out_idv_form_ok if step == :profile - fill_out_phone_form_ok if step == :phone - click_idv_continue - - expect(page).to have_content(t('idv.titles.select_verification')) if step == :profile - expect(page).to have_current_path(verify_address_path) if step == :profile - expect(page).to have_content(t('idv.titles.otp_delivery_method')) if step == :phone - expect(page).to have_current_path(verify_otp_delivery_method_path) if step == :phone - end - end - - def expect_page_to_have_warning_message - expect(page).to have_content t("idv.modal.#{step_locale_key}.heading") - expect(page).to have_content t("idv.modal.#{step_locale_key}.warning") - end - - def expect_page_to_have_warning_modal - expect(page).to have_css('.modal-warning', text: t("idv.modal.#{step_locale_key}.heading")) - expect(page).to have_css( - '.modal-warning', - text: strip_tags(t("idv.modal.#{step_locale_key}.warning")) - ) - end - - def dismiss_warning_modal - click_button t('idv.modal.button.warning') - end -end From a67b03a157bf78da308152d8a63cf0c858ffeb83 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Mon, 16 Apr 2018 12:33:21 -0500 Subject: [PATCH 15/39] Use shared examples to test failure to verify (#2101) **Why**: So that we can test that steps that rely on IdV jobs response correctly and consistently when the user's data cannot be verified --- spec/features/idv/fail_to_verify_spec.rb | 8 +++++ spec/features/idv/flow_spec.rb | 37 ------------------------ spec/features/idv/phone_spec.rb | 33 --------------------- 3 files changed, 8 insertions(+), 70 deletions(-) create mode 100644 spec/features/idv/fail_to_verify_spec.rb diff --git a/spec/features/idv/fail_to_verify_spec.rb b/spec/features/idv/fail_to_verify_spec.rb new file mode 100644 index 00000000000..4e50070940f --- /dev/null +++ b/spec/features/idv/fail_to_verify_spec.rb @@ -0,0 +1,8 @@ +require 'rails_helper' + +feature 'fail to verify', :idv_job do + include IdvStepHelper + + it_behaves_like 'fail to verify idv info', :profile + it_behaves_like 'fail to verify idv info', :phone +end diff --git a/spec/features/idv/flow_spec.rb b/spec/features/idv/flow_spec.rb index 004ee2a276e..da2b05de868 100644 --- a/spec/features/idv/flow_spec.rb +++ b/spec/features/idv/flow_spec.rb @@ -46,34 +46,6 @@ expect(user.reload.active_profile).to be_a(Profile) end - scenario 'vendor agent throws exception' do - first_name_to_trigger_exception = 'Fail' - - sign_in_and_2fa_user - - visit verify_session_path - - fill_out_idv_form_ok - fill_in 'profile_first_name', with: first_name_to_trigger_exception - - expect(Idv::ProfileJob).to receive(:perform_now).and_wrap_original do |perform, *args| - exception_raised = false - begin - perform.call(*args) - rescue RuntimeError => err - expect(err.message).to eq('Failed to contact proofing vendor') - exception_raised = true - ensure - expect(exception_raised).to eq(true) - end - end - - click_idv_continue - - expect(current_path).to eq(verify_session_result_path) - expect(page).to have_css('.modal-warning', text: t('idv.modal.sessions.heading')) - end - scenario 'profile steps is not re-entrant and are sticky on failure', :js do user = sign_in_and_2fa_user @@ -166,15 +138,6 @@ expect(page).to have_content(different_phone) end - scenario 'failed attempt shows flash message' do - sign_in_and_2fa_user - visit verify_session_path - fill_out_idv_form_fail - click_idv_continue - - expect(page).to have_content t('idv.modal.sessions.warning') - end - scenario 'closing previous address accordion clears inputs and toggles header', js: true do _user = sign_in_and_2fa_user diff --git a/spec/features/idv/phone_spec.rb b/spec/features/idv/phone_spec.rb index d34d09dd263..29a86a6be65 100644 --- a/spec/features/idv/phone_spec.rb +++ b/spec/features/idv/phone_spec.rb @@ -44,39 +44,6 @@ end end - context 'failing to verify 2FA phone' do - scenario 'requires verifying and confirming a different phone', :idv_job do - phone_number_that_will_fail_verification = '+1 (555) 555-5555' - - user = create( - :user, :signed_up, - phone: phone_number_that_will_fail_verification, - password: Features::SessionHelper::VALID_PASSWORD - ) - sign_in_and_2fa_user(user) - visit verify_session_path - fill_out_idv_form_ok - click_idv_continue - click_idv_address_choose_phone - - fill_in 'Phone', with: user.phone - click_idv_continue - - expect(page).to have_content(t('idv.modal.phone.heading')) - - fill_in 'Phone', with: '+1 (555) 555-5000' - click_idv_continue - - choose_idv_otp_delivery_method_sms - enter_correct_otp_code_for_user(user) - fill_in :user_password, with: user_password - click_continue - click_acknowledge_personal_key - - expect(current_path).to eq account_path - end - end - def complete_idv_profile_with_phone(phone) fill_out_idv_form_ok click_idv_continue From 459dca55fb9d4ae11aecc38e3f0f88d69ce5edad Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Mon, 16 Apr 2018 14:54:09 -0400 Subject: [PATCH 16/39] Replace Poltergeist/PhantomJS with Headless Chrome **Why**: - Poltergeist doesn't work with the latest version of webpacker - PhantomJS doesn't seem to be as actively maintained as the Chrome webdriver - Being able to test against Chrome better simulates how the app behaves in a browser that many of our customers use --- .circleci/config.yml | 8 - Dockerfile | 12 - Gemfile | 3 +- Gemfile.lock | 21 +- README.md | 8 - config/database.yml | 2 + knapsack_rspec_report.json | 613 +++++++++--------- spec/features/accessibility/idv_pages_spec.rb | 2 - spec/features/idv/flow_spec.rb | 6 +- spec/features/idv/interrupted_session_spec.rb | 27 - spec/features/saml/loa1_sso_spec.rb | 2 +- .../two_factor_authentication/sign_in_spec.rb | 17 +- .../users/regenerate_personal_key_spec.rb | 28 +- spec/features/users/sign_in_spec.rb | 8 +- spec/features/users/user_edit_spec.rb | 6 +- spec/features/users/user_profile_spec.rb | 2 +- spec/features/visitors/i18n_spec.rb | 12 +- spec/features/visitors/set_password_spec.rb | 2 +- spec/support/capybara.rb | 16 +- spec/support/features/idv_helper.rb | 9 +- .../idv_examples/cancel_at_idv_step.rb | 2 +- 21 files changed, 376 insertions(+), 430 deletions(-) delete mode 100644 spec/features/idv/interrupted_session_spec.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index 550ea53bfd3..7bc0f716e4b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,14 +54,6 @@ jobs: key: identity-idp-yarn-{{ checksum "yarn.lock" }} paths: - ~/.cache/yarn - - - run: - name: Install phantomjs - command: | - sudo curl --output /tmp/phantomjs https://s3.amazonaws.com/circle-downloads/phantomjs-2.1.1 - sudo chmod ugo+x /tmp/phantomjs - sudo ln -sf /tmp/phantomjs /usr/local/bin/phantomjs - - run: name: Install AWS CLI command: | diff --git a/Dockerfile b/Dockerfile index 275b6f66f20..dfae4e98b1a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,18 +19,6 @@ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update && apt-get install yarn -# PhantomJS is required for running tests -# TOOD(sbc): Create a separate production container without this. -ENV PHANTOMJS_SHA256 86dd9a4bf4aee45f1a84c9f61cf1947c1d6dce9b9e8d2a907105da7852460d2f - -RUN mkdir /usr/local/phantomjs \ - && curl -o phantomjs.tar.bz2 -L https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 \ - && echo "$PHANTOMJS_SHA256 *phantomjs.tar.bz2" | sha256sum -c - \ - && tar -xjf phantomjs.tar.bz2 -C /usr/local/phantomjs --strip-components=1 \ - && rm phantomjs.tar.bz2 - -RUN ln -s ../phantomjs/bin/phantomjs /usr/local/bin/ - WORKDIR /upaya COPY package.json /upaya diff --git a/Gemfile b/Gemfile index f1f88c267d4..fe3ad92ec7e 100644 --- a/Gemfile +++ b/Gemfile @@ -91,13 +91,14 @@ end group :test do gem 'axe-matchers', '~> 1.3.4' gem 'capybara-screenshot', github: 'mattheworiordan/capybara-screenshot' + gem 'capybara-selenium' + gem 'chromedriver-helper' gem 'codeclimate-test-reporter', require: false gem 'database_cleaner' gem 'email_spec' gem 'factory_bot_rails' gem 'fakefs', require: 'fakefs/safe' gem 'faker' - gem 'poltergeist' gem 'rack-test' gem 'rack_session_access' gem 'rails-controller-testing' diff --git a/Gemfile.lock b/Gemfile.lock index 24c8bcbac04..b8e2843259f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -124,6 +124,8 @@ GEM gyoku (>= 0.4.0) nokogiri american_date (1.1.1) + archive-zip (0.11.0) + io-like (~> 0.3.0) arel (8.0.0) ast (2.4.0) aws-partitions (1.70.0) @@ -176,12 +178,17 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (>= 2.0, < 4.0) + capybara-selenium (0.0.6) + capybara + selenium-webdriver childprocess (0.9.0) ffi (~> 1.0, >= 1.0.11) choice (0.2.0) + chromedriver-helper (1.2.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) chronic (0.10.2) chunky_png (1.3.8) - cliver (0.3.2) codeclimate-engine-rb (0.4.1) virtus (~> 1.0) codeclimate-test-reporter (1.0.8) @@ -313,6 +320,7 @@ GEM terminal-table (>= 1.5.1) ice_nine (0.11.2) iniparse (1.4.4) + io-like (0.3.0) jmespath (1.3.1) json (1.8.6) json-jwt (1.9.2) @@ -377,10 +385,6 @@ GEM phony_rails (0.14.6) activesupport (>= 3.0) phony (> 2.15) - poltergeist (1.17.0) - capybara (~> 2.1) - cliver (~> 0.3.1) - websocket-driver (>= 0.2.0) powerpack (0.1.1) premailer (1.11.1) addressable @@ -503,6 +507,7 @@ GEM ruby_dep (1.5.0) ruby_parser (3.10.1) sexp_processor (~> 4.9) + rubyzip (1.2.1) safe_yaml (1.0.4) safely_block (0.2.1) errbase @@ -530,6 +535,9 @@ GEM secure_headers (3.7.3) useragent securecompare (1.0.0) + selenium-webdriver (3.11.0) + childprocess (~> 0.5) + rubyzip (~> 1.2) sexp_processor (4.10.0) shellany (0.0.1) shoulda-matchers (3.1.2) @@ -670,6 +678,8 @@ DEPENDENCIES bullet bummr capybara-screenshot! + capybara-selenium + chromedriver-helper codeclimate-test-reporter database_cleaner derailed @@ -704,7 +714,6 @@ DEPENDENCIES pg phonelib phony_rails - poltergeist premailer-rails proofer! pry-byebug diff --git a/README.md b/README.md index 7d573675a10..9672ef494d1 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,6 @@ A Identity Management System powering login.gov. - [Redis 2.8+](http://redis.io/) - [Node.js v8.x.x](https://nodejs.org) -Testing dependencies: -- [PhantomJS](http://phantomjs.org) - #### Setting up and running the app 1. Make sure you have a working development environment with all the @@ -158,11 +155,6 @@ on a more robust and user-friendly way to switch between locales. ### Running Tests -Make sure you have [PhantomJS](http://phantomjs.org) installed prior to running -tests. On OS X, PhantomJS can be installed with Homebrew via: `brew install -phantomjs`. For other platforms, refer to the [PhantomJS download -page](http://phantomjs.org/download.html). - To run all the tests: ``` diff --git a/config/database.yml b/config/database.yml index b6829c2b5b6..2466c51ed14 100644 --- a/config/database.yml +++ b/config/database.yml @@ -28,6 +28,8 @@ development: test: <<: *defaults + pool: 10 + checkout_timeout: 10 production: <<: *defaults diff --git a/knapsack_rspec_report.json b/knapsack_rspec_report.json index 538bdd99a82..5e927260abf 100644 --- a/knapsack_rspec_report.json +++ b/knapsack_rspec_report.json @@ -1,307 +1,310 @@ { - "spec/mailers/user_mailer_spec.rb": 3.986692428588867, - "spec/controllers/health/health_controller_spec.rb": 0.0879204273223877, - "spec/features/visitors/password_recovery_spec.rb": 18.320884704589844, - "spec/services/store_sp_metadata_in_session_spec.rb": 0.03163957595825195, - "spec/helpers/locale_helper_spec.rb": 0.00511479377746582, - "spec/lib/yaml_normalizer_spec.rb": 0.012772321701049805, - "spec/forms/totp_verification_form_spec.rb": 0.048506975173950195, - "spec/services/parse_controller_from_referer_spec.rb": 0.0026540756225585938, - "spec/views/sign_up/personal_keys/show.html.slim_spec.rb": 0.2504265308380127, - "spec/services/twilio_service_spec.rb": 0.021703004837036133, - "spec/forms/password_reset_email_form_spec.rb": 0.051154375076293945, - "spec/views/shared/_flashes.html.slim_spec.rb": 0.015145540237426758, - "spec/mailers/custom_devise_mailer_spec.rb": 0.02736043930053711, - "spec/controllers/reactivate_account_controller_spec.rb": 0.27582335472106934, - "spec/controllers/users/reset_passwords_controller_spec.rb": 1.8172168731689453, - "spec/services/access_token_verifier_spec.rb": 0.04410696029663086, - "spec/views/sign_up/emails/show.html.slim_spec.rb": 0.027134180068969727, - "spec/views/users/totp_setup/new.html.slim_spec.rb": 0.06975769996643066, - "spec/services/email_notifier_spec.rb": 0.07540345191955566, - "spec/controllers/users/verify_account_controller_spec.rb": 0.24046969413757324, - "spec/requests/invalid_encoding_spec.rb": 0.01710224151611328, - "spec/lib/worker_health_checker_spec.rb": 0.03649735450744629, - "spec/services/marketing_site_spec.rb": 0.011338233947753906, - "spec/features/accessibility/user_pages_spec.rb": 10.79332423210144, - "spec/services/service_provider_updater_spec.rb": 0.10974884033203125, - "spec/services/session_encryptor_spec.rb": 0.08093667030334473, - "spec/services/pii/password_encryptor_spec.rb": 0.05370473861694336, - "spec/features/users/user_profile_spec.rb": 6.8305253982543945, - "spec/features/idv/failed_job_spec.rb": 0.31787729263305664, - "spec/requests/redirects_spec.rb": 0.010896682739257812, - "spec/features/saml/loa3_sso_spec.rb": 6.205901145935059, - "spec/views/devise/passwords/edit.html.slim_spec.rb": 0.16029071807861328, - "spec/forms/verify_account_form_spec.rb": 0.3789637088775635, - "spec/models/agency_identity_spec.rb": 0.01060032844543457, - "spec/lib/encrypted_sidekiq_redis_spec.rb": 0.5380933284759521, - "spec/forms/idv/profile_form_spec.rb": 0.6596782207489014, - "spec/lib/feature_management_spec.rb": 0.05297350883483887, - "spec/controllers/reauthn_required_controller_spec.rb": 0.14781570434570312, - "spec/presenters/verify/otp_delivery_method_presenter_spec.rb": 0.0024442672729492188, - "spec/views/sign_up/registrations/new.html.slim_spec.rb": 0.043694496154785156, - "spec/helpers/application_helper_spec.rb": 0.011135101318359375, - "spec/controllers/accounts_controller_spec.rb": 0.17741727828979492, - "spec/services/completions_decider_spec.rb": 0.014150619506835938, - "spec/features/idv/max_attempts_spec.rb": 3.353008508682251, - "spec/features/idv/usps_verification_spec.rb": 4.886984825134277, - "spec/views/sign_up/passwords/new.html.slim_spec.rb": 0.14666056632995605, - "spec/controllers/sign_up/completions_controller_spec.rb": 0.3774387836456299, - "spec/controllers/users/emails_controller_spec.rb": 1.016021966934204, - "spec/controllers/openid_connect/user_info_controller_spec.rb": 0.0719907283782959, - "spec/features/visitors/phone_confirmation_spec.rb": 2.010852098464966, - "spec/services/create_account_verified_event_spec.rb": 0.04576611518859863, - "spec/services/saml_request_validator_spec.rb": 0.019919633865356445, - "spec/controllers/users/totp_setup_controller_spec.rb": 2.0150365829467773, - "spec/models/agency_spec.rb": 0.007433176040649414, - "spec/controllers/sign_up/emails_controller_spec.rb": 0.026720285415649414, - "spec/forms/reset_password_form_spec.rb": 1.023256778717041, - "spec/services/encrypted_attribute_spec.rb": 0.056391000747680664, - "spec/controllers/verify/come_back_later_controller_spec.rb": 0.04928183555603027, - "spec/services/id_token_builder_spec.rb": 0.22456741333007812, - "spec/features/visitors/navigation_spec.rb": 0.044025421142578125, - "spec/view_models/sign_up_completions_show_spec.rb": 0.0949711799621582, - "spec/jobs/sms_otp_sender_job_spec.rb": 0.012763261795043945, - "spec/services/database_health_checker_spec.rb": 0.004609823226928711, - "spec/models/usps_confirmation_code_spec.rb": 0.19820117950439453, - "spec/views/two_factor_authentication_setup/index.html.slim_spec.rb": 0.06401348114013672, - "spec/services/pii/encryptor_spec.rb": 0.005594730377197266, - "spec/models/nonexistent_user_spec.rb": 0.00439000129699707, - "spec/services/analytics_spec.rb": 0.05577898025512695, - "spec/controllers/concerns/user_session_context_spec.rb": 0.008252859115600586, - "spec/features/two_factor_authentication/sign_in_via_personal_key_spec.rb": 0.6739380359649658, - "spec/services/idv/upcase_vendor_env_vars_spec.rb": 0.003527402877807617, - "spec/views/layouts/user_mailer.html.slim_spec.rb": 0.13714957237243652, - "spec/jobs/voice_otp_sender_job_spec.rb": 0.4360339641571045, - "spec/lib/tasks/dev_rake_spec.rb": 0.811450719833374, - "spec/view_models/account_show_spec.rb": 0.02911853790283203, - "spec/features/saml/loa1_sso_spec.rb": 14.987728118896484, - "spec/presenters/saml_request_presenter_spec.rb": 0.0062482357025146484, - "spec/features/load_testing/email_sign_up_spec.rb": 0.5362625122070312, - "spec/services/secure_headers_whitelister_spec.rb": 0.0058612823486328125, - "spec/views/two_factor_authentication/personal_key_verification/show.html.slim_spec.rb": 0.14450454711914062, - "spec/presenters/two_factor_auth_code/authenticator_delivery_presenter_spec.rb": 0.004559040069580078, - "spec/features/idv/previous_address_spec.rb": 0.8982112407684326, - "spec/services/uri_service_spec.rb": 0.007273435592651367, - "spec/services/usps_confirmation_maker_spec.rb": 0.1388988494873047, - "spec/lib/i18n_override_spec.rb": 0.005015850067138672, - "spec/models/authorization_spec.rb": 0.1811199188232422, - "spec/forms/update_user_email_form_spec.rb": 1.088576078414917, - "spec/features/saml/idp_initiated_slo_spec.rb": 4.874002933502197, - "spec/services/key_rotator/attribute_encryption_spec.rb": 0.04194307327270508, - "spec/views/verify/_hardfail4.html.slim_spec.rb": 0.014057159423828125, - "spec/forms/otp_delivery_selection_form_spec.rb": 0.13730192184448242, - "spec/views/sign_up/email_resend/new.html.slim_spec.rb": 0.019286155700683594, - "spec/services/null_twilio_client_spec.rb": 0.0022110939025878906, - "spec/controllers/verify/confirmations_controller_spec.rb": 0.7694520950317383, - "spec/controllers/users/verify_personal_key_controller_spec.rb": 0.4001772403717041, - "spec/views/shared/_footer_lite.html.slim_spec.rb": 0.0685584545135498, - "spec/views/two_factor_authentication/shared/max_login_attempts_reached.html.erb_spec.rb": 0.0118255615234375, - "spec/features/session/decryption_spec.rb": 0.2482471466064453, - "spec/features/visitors/set_password_spec.rb": 2.215163469314575, - "spec/services/personal_key_generator_spec.rb": 0.3567509651184082, - "spec/presenters/two_factor_auth_code/generic_delivery_presenter_spec.rb": 0.004991292953491211, - "spec/services/attribute_asserter_spec.rb": 0.9589550495147705, - "spec/requests/openid_connect_cors_spec.rb": 0.1798079013824463, - "spec/presenters/openid_connect_configuration_presenter_spec.rb": 0.002230405807495117, - "spec/features/users/password_recovery_via_recovery_code_spec.rb": 18.8932204246521, - "spec/controllers/application_controller_spec.rb": 2.604541063308716, - "spec/services/otp_rate_limiter_spec.rb": 0.11582660675048828, - "spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb": 0.03949570655822754, - "spec/services/identity_linker_spec.rb": 0.1719038486480713, - "spec/views/devise/passwords/new.html.slim_spec.rb": 0.07641816139221191, - "spec/controllers/users/verify_password_controller_spec.rb": 0.38100528717041016, - "spec/decorators/event_decorator_spec.rb": 0.002351522445678711, - "spec/features/two_factor_authentication/sign_in_spec.rb": 14.74148154258728, - "spec/views/forgot_password/show.html.slim_spec.rb": 0.08753395080566406, - "spec/services/pii/fingerprinter_spec.rb": 0.017343521118164062, - "spec/controllers/openid_connect/logout_controller_spec.rb": 0.24807190895080566, - "spec/controllers/openid_connect/certs_controller_spec.rb": 0.009980201721191406, - "spec/controllers/sign_up/email_resend_controller_spec.rb": 0.8763036727905273, - "spec/features/idv/phone_spec.rb": 5.999727487564087, - "spec/services/openid_connect_attribute_scoper_spec.rb": 0.014177083969116211, - "spec/controllers/concerns/idv_step_concern_spec.rb": 0.41180920600891113, - "spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb": 0.6080248355865479, - "spec/forms/user_phone_form_spec.rb": 0.6127245426177979, - "spec/forms/update_user_password_form_spec.rb": 1.0144028663635254, - "spec/controllers/verify/address_controller_spec.rb": 0.10412955284118652, - "spec/lib/production_database_configuration_spec.rb": 0.03624248504638672, - "spec/controllers/health/database_controller_spec.rb": 0.021276473999023438, - "spec/services/usps_confirmation_entry_spec.rb": 0.02858877182006836, - "spec/presenters/fully_signed_in_modal_presenter_spec.rb": 0.005531787872314453, - "spec/lib/fingerprinter_spec.rb": 0.002787351608276367, - "spec/features/two_factor_authentication/change_factor_spec.rb": 8.18818473815918, - "spec/models/profile_spec.rb": 0.801518440246582, - "spec/controllers/health/workers_controller_spec.rb": 0.017624378204345703, - "spec/controllers/openid_connect/authorization_controller_spec.rb": 0.746269941329956, - "spec/views/verify/_no_sp_hardfail.html.slim_spec.rb": 0.01296687126159668, - "spec/decorators/service_provider_session_decorator_spec.rb": 0.03127765655517578, - "spec/features/accessibility/idv_pages_spec.rb": 8.181209087371826, - "spec/features/session/timeout_spec.rb": 0.12663817405700684, - "spec/features/visitors/sign_up_with_email_spec.rb": 2.129502058029175, - "spec/forms/resend_email_confirmation_form_spec.rb": 0.06794166564941406, - "spec/controllers/voice/otp_controller_spec.rb": 3.840808153152466, - "spec/lib/headers_filter_spec.rb": 0.0028104782104492188, - "spec/features/users/sign_in_spec.rb": 29.498939037322998, - "spec/services/request_key_manager_spec.rb": 0.0025196075439453125, - "spec/services/random_phrase_spec.rb": 0.011853933334350586, - "spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb": 0.6623010635375977, - "spec/features/saml/loa1/account_creation_spec.rb": 0.9472696781158447, - "spec/controllers/users/two_factor_authentication_setup_controller_spec.rb": 0.2068028450012207, - "spec/forms/idv/address_delivery_method_form_spec.rb": 0.004529714584350586, - "spec/decorators/identity_decorator_spec.rb": 0.04321479797363281, - "spec/controllers/sign_out_controller_spec.rb": 0.04851484298706055, - "spec/models/service_provider_spec.rb": 0.062378883361816406, - "spec/features/openid_connect/openid_connect_spec.rb": 22.434062480926514, - "spec/controllers/verify/sessions_controller_spec.rb": 1.210376262664795, - "spec/deploy/activate_spec.rb": 0.09220552444458008, - "spec/services/openid_connect_redirector_spec.rb": 0.026537418365478516, - "spec/services/update_user_spec.rb": 0.017661571502685547, - "spec/services/email_confirmation_token_validator_spec.rb": 0.03945326805114746, - "spec/views/accounts/show.html.slim_spec.rb": 0.9790208339691162, - "spec/services/service_provider_config_spec.rb": 0.002758026123046875, - "spec/models/service_provider_request_spec.rb": 0.006059885025024414, - "spec/views/layouts/application.html.slim_spec.rb": 2.376565456390381, - "spec/decorators/usps_decorator_spec.rb": 0.061933040618896484, - "spec/lib/aws/ses_spec.rb": 0.02424454689025879, - "spec/controllers/verify/otp_delivery_method_controller_spec.rb": 0.21142148971557617, - "spec/services/phone_number_capabilities_spec.rb": 0.007938623428344727, - "spec/lib/tasks/rotate_rake_spec.rb": 0.05372190475463867, - "spec/services/agency_seeder_spec.rb": 0.3446023464202881, - "spec/jobs/idv/phone_job_spec.rb": 0.01353311538696289, - "spec/services/idv/usps_mail_spec.rb": 0.12196493148803711, - "spec/lib/config_validator_spec.rb": 0.0029556751251220703, - "spec/controllers/users_controller_spec.rb": 0.1290736198425293, - "spec/services/link_agency_identities_spec.rb": 0.3420584201812744, - "spec/services/user_access_key_spec.rb": 0.2587099075317383, - "spec/services/idv/profile_step_spec.rb": 0.1603386402130127, - "spec/services/pii/cipher_spec.rb": 0.00513911247253418, - "spec/features/accessibility/visitor_pages_spec.rb": 1.5659127235412598, - "spec/features/users/verify_profile_spec.rb": 1.837984323501587, - "spec/controllers/forgot_password_controller_spec.rb": 0.022332191467285156, - "spec/services/service_provider_seeder_spec.rb": 0.30271053314208984, - "spec/presenters/openid_connect_certs_presenter_spec.rb": 0.0016524791717529297, - "spec/requests/rack_attack_spec.rb": 1.4524259567260742, - "spec/forms/openid_connect_authorize_form_spec.rb": 0.15820026397705078, - "spec/services/pii/cacher_spec.rb": 0.41342902183532715, - "spec/decorators/user_decorator_spec.rb": 0.6349921226501465, - "spec/features/saml/saml_spec.rb": 7.28932785987854, - "spec/features/users/sign_up_spec.rb": 9.32424545288086, - "spec/services/multi_health_checker_spec.rb": 0.004421234130859375, - "spec/services/usps_uploader_spec.rb": 0.028224945068359375, - "spec/services/idv/session_spec.rb": 0.2105872631072998, - "spec/views/users/emails/edit.html.slim_spec.rb": 0.048952579498291016, - "spec/views/shared/_nav_lite.html.slim_spec.rb": 0.010764598846435547, - "spec/features/users/sign_out_spec.rb": 0.17313599586486816, - "spec/models/usps_confirmation_spec.rb": 0.02008819580078125, - "spec/controllers/sign_up/personal_keys_controller_spec.rb": 0.26556921005249023, - "spec/forms/openid_connect_token_form_spec.rb": 0.967048168182373, - "spec/presenters/eastern_time_presenter_spec.rb": 0.001943826675415039, - "spec/services/active_profile_encryptor_spec.rb": 0.06891202926635742, - "spec/services/basic_auth_url_spec.rb": 0.004117012023925781, - "spec/controllers/openid_connect/token_controller_spec.rb": 0.18847179412841797, - "spec/views/reactivate_account/index.html.slim_spec.rb": 0.047515869140625, - "spec/view_models/verify/base_spec.rb": 0.007755756378173828, - "spec/services/pii/session_store_spec.rb": 0.009895563125610352, - "spec/views/users/delete/show.html.slim_spec.rb": 0.18936753273010254, - "spec/models/user_spec.rb": 0.67449951171875, - "spec/presenters/utc_time_presenter_spec.rb": 0.002670764923095703, - "spec/controllers/verify/review_controller_spec.rb": 1.2566099166870117, - "spec/services/pii/re_encryptor_spec.rb": 0.158766508102417, - "spec/features/saml/sp_initiated_slo_spec.rb": 13.137084722518921, - "spec/controllers/sign_up/passwords_controller_spec.rb": 0.11266326904296875, - "spec/views/devise/mailer/confirmation_instructions.html.slim_spec.rb": 0.15828180313110352, - "spec/jobs/idv/profile_job_spec.rb": 0.03270840644836426, - "spec/svg_spec.rb": 0.19542670249938965, - "spec/controllers/sign_up/registrations_controller_spec.rb": 1.4633290767669678, - "spec/models/otp_requests_tracker_spec.rb": 0.03232693672180176, - "spec/views/two_factor_authentication/totp_verification/show.html.slim_spec.rb": 0.3327643871307373, - "spec/services/reactivate_account_session_spec.rb": 0.21663618087768555, - "spec/views/devise/sessions/new.html.slim_spec.rb": 0.3100268840789795, - "spec/features/idv/flow_spec.rb": 20.391412258148193, - "spec/views/verify/come_back_later/show.html.slim_spec.rb": 0.044745445251464844, - "spec/controllers/users/two_factor_authentication_controller_spec.rb": 1.154895544052124, - "spec/features/visitors/email_confirmation_spec.rb": 3.8671469688415527, - "spec/forms/personal_key_form_spec.rb": 0.13913989067077637, - "spec/requests/page_not_found_spec.rb": 0.05068850517272949, - "spec/forms/register_user_email_form_spec.rb": 0.429274320602417, - "spec/views/sign_up/completions/show.html.slim_spec.rb": 0.14742016792297363, - "spec/features/idv/interrupted_session_spec.rb": 0.8815438747406006, - "spec/controllers/verify/phone_controller_spec.rb": 0.5535168647766113, - "spec/controllers/openid_connect/configuration_controller_spec.rb": 0.016040325164794922, - "spec/controllers/users/phones_controller_spec.rb": 0.2789463996887207, - "spec/services/idv/phone_step_spec.rb": 0.06299352645874023, - "spec/services/pii/attributes_spec.rb": 0.05000448226928711, - "spec/services/idv/profile_maker_spec.rb": 0.13973498344421387, - "spec/features/idv/account_creation_spec.rb": 58.99188446998596, - "spec/views/sign_up/registrations/show.html.slim_spec.rb": 0.11463713645935059, - "spec/services/form_response_spec.rb": 0.012616395950317383, - "spec/view_models/forgot_password_show_spec.rb": 0.0018150806427001953, - "spec/features/account_history_spec.rb": 0.3339567184448242, - "spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb": 2.3806405067443848, - "spec/features/users/user_edit_spec.rb": 4.289666175842285, - "spec/controllers/verify_controller_spec.rb": 0.5461723804473877, - "spec/features/visitors/i18n_spec.rb": 0.7698218822479248, - "spec/forms/openid_connect_logout_form_spec.rb": 0.49080419540405273, - "spec/controllers/mfa_confirmation_controller_spec.rb": 0.2582416534423828, - "spec/services/file_encryptor_spec.rb": 0.0436246395111084, - "spec/views/users/phones/edit.html.slim_spec.rb": 0.09602212905883789, - "spec/requests/i18n_spec.rb": 0.10391592979431152, - "spec/forms/idv/phone_form_spec.rb": 1.2381629943847656, - "spec/forms/totp_setup_form_spec.rb": 0.04805493354797363, - "spec/presenters/partially_signed_in_modal_presenter_spec.rb": 0.008043527603149414, - "spec/models/null_identity_spec.rb": 0.0018167495727539062, - "spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb": 0.44376420974731445, - "spec/services/usps_exporter_spec.rb": 0.24862337112426758, - "spec/lib/i18n_converter_spec.rb": 0.02085423469543457, - "spec/models/identity_spec.rb": 0.4868595600128174, - "spec/presenters/openid_connect_user_info_presenter_spec.rb": 0.09418439865112305, - "spec/i18n_spec.rb": 91.71177530288696, - "spec/views/verify/usps/index.html.slim_spec.rb": 0.030670881271362305, - "spec/controllers/sign_up/email_confirmations_controller_spec.rb": 0.589585542678833, - "spec/views/verify/fail.html.slim_spec.rb": 0.06615209579467773, - "spec/lib/no_retry_jobs_spec.rb": 0.006979465484619141, - "spec/controllers/users/delete_controller_spec.rb": 0.31728458404541016, - "spec/forms/otp_verification_form_spec.rb": 0.05375027656555176, - "spec/controllers/saml_idp_controller_spec.rb": 10.063886165618896, - "spec/views/shared/_nav_branded.html.slim_spec.rb": 0.020441532135009766, - "spec/routing/id_verification_routing_spec.rb": 4.729284286499023, - "spec/controllers/users/sessions_controller_spec.rb": 1.4109222888946533, - "spec/lib/sidekiq_logging_formatter_spec.rb": 0.004430055618286133, - "spec/views/accounts/_nav_auth.html.slim_spec.rb": 0.07155799865722656, - "spec/controllers/verify/usps_controller_spec.rb": 0.17763805389404297, - "spec/models/null_service_provider_spec.rb": 0.01243448257446289, - "spec/controllers/users/personal_keys_controller_spec.rb": 0.3106074333190918, - "spec/requests/openid_connect_authorize_spec.rb": 0.49600768089294434, - "spec/services/encrypted_key_maker_spec.rb": 0.0755319595336914, - "spec/services/request_password_reset_spec.rb": 0.052195072174072266, - "spec/services/agency_identity_linker_spec.rb": 0.3087284564971924, - "spec/services/idv/attempter_spec.rb": 0.10491800308227539, - "spec/features/users/regenerate_personal_key_spec.rb": 7.699583053588867, - "spec/decorators/openid_connect_authorize_decorator_spec.rb": 0.0014214515686035156, - "spec/services/phone_formatter_spec.rb": 0.0071485042572021484, - "spec/features/twilio/voice_spec.rb": 0.44109559059143066, - "spec/controllers/users/passwords_controller_spec.rb": 0.4353454113006592, - "spec/services/idv/vendor_result_spec.rb": 0.007738590240478516, - "spec/services/pii/attribute_spec.rb": 0.0023670196533203125, - "spec/features/accessibility/static_pages_spec.rb": 1.085855484008789, - "spec/services/key_rotator/hmac_fingerprinter_spec.rb": 0.1556539535522461, - "spec/views/verify/review/new.html.slim_spec.rb": 0.1504976749420166, - "spec/views/shared/_address.html.slim_spec.rb": 0.0063664913177490234, - "spec/helpers/session_timeout_warning_helper_spec.rb": 0.016561269760131836, - "spec/services/vendor_validator_result_storage_spec.rb": 0.005162239074707031, - "spec/views/verify/activated.html.slim_spec.rb": 0.015904664993286133, - "spec/requests/headers_spec.rb": 0.3333923816680908, - "spec/controllers/service_provider_controller_spec.rb": 0.04184269905090332, - "spec/features/visitors/resend_email_confirmation_spec.rb": 0.9515554904937744, - "spec/config/initializers/zxcvbn_overrides_spec.rb": 0.16351008415222168, - "spec/decorators/session_decorator_spec.rb": 0.015351057052612305, - "spec/requests/edit_user_spec.rb": 2.8019626140594482, - "spec/views/users/passwords/edit.html.slim_spec.rb": 0.11353373527526855, - "spec/services/idv/submit_idv_job_spec.rb": 0.028253555297851562, - "spec/models/event_spec.rb": 0.008981704711914062, - "spec/requests/constrained_route_spec.rb": 0.5246772766113281, - "spec/services/pii/nist_encryption_spec.rb": 0.06160473823547363, - "spec/forms/password_form_spec.rb": 1.0038785934448242, - "spec/controllers/users/verify_profile_phone_controller_spec.rb": 0.15323495864868164, - "spec/controllers/pages_controller_spec.rb": 0.07193756103515625 + "spec/view_models/sign_up_completions_show_spec.rb": 0.2717890739440918, + "spec/views/two_factor_authentication/otp_verification/show.html.slim_spec.rb": 0.5840790271759033, + "spec/services/idv/session_spec.rb": 0.17386341094970703, + "spec/views/sign_up/emails/show.html.slim_spec.rb": 2.23313570022583, + "spec/services/otp_rate_limiter_spec.rb": 0.12606430053710938, + "spec/views/devise/sessions/new.html.slim_spec.rb": 0.2266993522644043, + "spec/services/service_provider_seeder_spec.rb": 0.3269057273864746, + "spec/views/two_factor_authentication/totp_verification/show.html.slim_spec.rb": 0.24782276153564453, + "spec/features/session/decryption_spec.rb": 2.7980213165283203, + "spec/services/service_provider_config_spec.rb": 0.03393101692199707, + "spec/features/idv/failed_job_spec.rb": 0.426283597946167, + "spec/controllers/mfa_confirmation_controller_spec.rb": 0.2382512092590332, + "spec/features/users/password_recovery_via_recovery_code_spec.rb": 28.647499799728394, + "spec/i18n_spec.rb": 92.11280918121338, + "spec/controllers/users/passwords_controller_spec.rb": 0.5340871810913086, + "spec/models/identity_spec.rb": 0.44906115531921387, + "spec/views/verify/review/new.html.slim_spec.rb": 0.13842535018920898, + "spec/services/idv/profile_step_spec.rb": 0.1894080638885498, + "spec/decorators/event_decorator_spec.rb": 0.0025861263275146484, + "spec/services/marketing_site_spec.rb": 0.012434959411621094, + "spec/services/key_rotator/attribute_encryption_spec.rb": 0.06510138511657715, + "spec/views/verify/activated.html.slim_spec.rb": 0.015650033950805664, + "spec/forms/personal_key_form_spec.rb": 0.08583760261535645, + "spec/features/visitors/sign_up_with_email_spec.rb": 2.205264091491699, + "spec/requests/openid_connect_authorize_spec.rb": 0.5126087665557861, + "spec/models/null_identity_spec.rb": 0.0017321109771728516, + "spec/models/nonexistent_user_spec.rb": 0.0045719146728515625, + "spec/view_models/verify/base_spec.rb": 0.007018327713012695, + "spec/services/multi_health_checker_spec.rb": 0.004386425018310547, + "spec/presenters/two_factor_auth_code/authenticator_delivery_presenter_spec.rb": 0.004437923431396484, + "spec/features/users/regenerate_personal_key_spec.rb": 8.499186754226685, + "spec/forms/totp_verification_form_spec.rb": 0.045496463775634766, + "spec/views/devise/mailer/confirmation_instructions.html.slim_spec.rb": 0.12572288513183594, + "spec/features/visitors/navigation_spec.rb": 0.06532120704650879, + "spec/services/pii/cacher_spec.rb": 0.5400276184082031, + "spec/requests/i18n_spec.rb": 0.22847795486450195, + "spec/services/usps_confirmation_entry_spec.rb": 0.029170751571655273, + "spec/models/profile_spec.rb": 0.9006211757659912, + "spec/controllers/sign_up/email_resend_controller_spec.rb": 0.9662613868713379, + "spec/presenters/saml_request_presenter_spec.rb": 0.008174896240234375, + "spec/presenters/two_factor_auth_code/generic_delivery_presenter_spec.rb": 0.00560307502746582, + "spec/forms/idv/phone_form_spec.rb": 1.3831677436828613, + "spec/services/idv/phone_step_spec.rb": 0.05513644218444824, + "spec/forms/password_form_spec.rb": 1.0413036346435547, + "spec/controllers/sign_out_controller_spec.rb": 0.06468772888183594, + "spec/models/event_spec.rb": 0.007929325103759766, + "spec/features/two_factor_authentication/sign_in_via_personal_key_spec.rb": 0.6508169174194336, + "spec/features/two_factor_authentication/sign_in_spec.rb": 20.160819053649902, + "spec/mailers/custom_devise_mailer_spec.rb": 0.03110051155090332, + "spec/controllers/openid_connect/user_info_controller_spec.rb": 0.10117340087890625, + "spec/forms/openid_connect_authorize_form_spec.rb": 0.16688299179077148, + "spec/controllers/verify/confirmations_controller_spec.rb": 0.8487775325775146, + "spec/services/random_phrase_spec.rb": 0.012439250946044922, + "spec/config/initializers/zxcvbn_overrides_spec.rb": 0.177870512008667, + "spec/services/email_notifier_spec.rb": 0.09134316444396973, + "spec/views/shared/_footer_lite.html.slim_spec.rb": 0.05005693435668945, + "spec/services/pii/session_store_spec.rb": 0.009768009185791016, + "spec/features/saml/loa1_sso_spec.rb": 16.86662197113037, + "spec/views/shared/_nav_branded.html.slim_spec.rb": 0.020214319229125977, + "spec/lib/deploy/activate_spec.rb": 0.12923336029052734, + "spec/services/null_twilio_client_spec.rb": 0.0027582645416259766, + "spec/lib/production_database_configuration_spec.rb": 0.03685331344604492, + "spec/features/idv/flow_spec.rb": 21.887020587921143, + "spec/views/users/delete/show.html.slim_spec.rb": 0.1173393726348877, + "spec/controllers/pages_controller_spec.rb": 0.054251909255981445, + "spec/controllers/users/two_factor_authentication_controller_spec.rb": 0.9602196216583252, + "spec/controllers/openid_connect/authorization_controller_spec.rb": 0.6995484828948975, + "spec/features/accessibility/static_pages_spec.rb": 0.8657140731811523, + "spec/models/otp_requests_tracker_spec.rb": 0.02229142189025879, + "spec/forms/idv/profile_form_spec.rb": 0.6419081687927246, + "spec/services/twilio_service_spec.rb": 0.016718387603759766, + "spec/services/pii/fingerprinter_spec.rb": 0.018826007843017578, + "spec/decorators/user_decorator_spec.rb": 0.5987403392791748, + "spec/services/link_agency_identities_spec.rb": 0.37624073028564453, + "spec/requests/invalid_encoding_spec.rb": 0.01836991310119629, + "spec/forms/user_phone_form_spec.rb": 0.61818528175354, + "spec/presenters/openid_connect_certs_presenter_spec.rb": 0.001890420913696289, + "spec/forms/update_user_email_form_spec.rb": 0.9165174961090088, + "spec/controllers/openid_connect/certs_controller_spec.rb": 0.017673015594482422, + "spec/features/load_testing/email_sign_up_spec.rb": 0.6359694004058838, + "spec/services/pii/cipher_spec.rb": 0.005825519561767578, + "spec/controllers/service_provider_controller_spec.rb": 0.05147576332092285, + "spec/controllers/users/reset_passwords_controller_spec.rb": 1.5682215690612793, + "spec/forms/verify_account_form_spec.rb": 0.44394826889038086, + "spec/features/saml/saml_spec.rb": 6.186649560928345, + "spec/controllers/openid_connect/logout_controller_spec.rb": 0.3190040588378906, + "spec/controllers/saml_idp_controller_spec.rb": 9.652611494064331, + "spec/controllers/users/totp_setup_controller_spec.rb": 4.6563804149627686, + "spec/controllers/health/database_controller_spec.rb": 0.022853612899780273, + "spec/views/verify/_no_sp_hardfail.html.slim_spec.rb": 0.01135563850402832, + "spec/controllers/users/verify_password_controller_spec.rb": 0.3873896598815918, + "spec/controllers/health/health_controller_spec.rb": 0.02443838119506836, + "spec/controllers/verify/usps_controller_spec.rb": 0.18081307411193848, + "spec/services/update_user_spec.rb": 0.0175628662109375, + "spec/features/idv/sp_handoff_spec.rb": 12.92354702949524, + "spec/decorators/usps_decorator_spec.rb": 0.0765383243560791, + "spec/controllers/sign_up/email_confirmations_controller_spec.rb": 0.4902200698852539, + "spec/services/create_account_verified_event_spec.rb": 0.04495978355407715, + "spec/lib/tasks/dev_rake_spec.rb": 1.1387667655944824, + "spec/services/reactivate_account_session_spec.rb": 0.12302851676940918, + "spec/features/idv/cancel_idv_step_spec.rb": 37.323952198028564, + "spec/views/two_factor_authentication_setup/index.html.slim_spec.rb": 0.0622556209564209, + "spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb": 2.1082053184509277, + "spec/features/users/user_edit_spec.rb": 4.043466567993164, + "spec/services/usps_uploader_spec.rb": 0.03351736068725586, + "spec/views/users/phones/edit.html.slim_spec.rb": 0.08946490287780762, + "spec/features/idv/account_creation_spec.rb": 36.92189311981201, + "spec/services/pii/encryptor_spec.rb": 0.005901813507080078, + "spec/presenters/partially_signed_in_modal_presenter_spec.rb": 0.004222393035888672, + "spec/controllers/verify/otp_delivery_method_controller_spec.rb": 0.2722451686859131, + "spec/lib/fingerprinter_spec.rb": 0.0032706260681152344, + "spec/views/verify/fail.html.slim_spec.rb": 0.04333615303039551, + "spec/controllers/verify/address_controller_spec.rb": 0.11769819259643555, + "spec/features/visitors/i18n_spec.rb": 5.561328887939453, + "spec/features/idv/previous_address_spec.rb": 1.043553352355957, + "spec/views/verify/come_back_later/show.html.slim_spec.rb": 0.03298640251159668, + "spec/controllers/reauthn_required_controller_spec.rb": 0.15790557861328125, + "spec/views/users/emails/edit.html.slim_spec.rb": 0.04601764678955078, + "spec/lib/i18n_converter_spec.rb": 0.016336679458618164, + "spec/features/two_factor_authentication/change_factor_spec.rb": 7.503625154495239, + "spec/controllers/verify/sessions_controller_spec.rb": 1.2815282344818115, + "spec/controllers/users/delete_controller_spec.rb": 0.2963244915008545, + "spec/features/idv/max_attempts_spec.rb": 56.09463858604431, + "spec/controllers/users/verify_personal_key_controller_spec.rb": 0.41304659843444824, + "spec/features/visitors/password_recovery_spec.rb": 13.98985767364502, + "spec/controllers/reactivate_account_controller_spec.rb": 0.21948647499084473, + "spec/services/agency_seeder_spec.rb": 0.04817557334899902, + "spec/lib/tasks/rotate_rake_spec.rb": 0.04617738723754883, + "spec/features/accessibility/idv_pages_spec.rb": 10.525515079498291, + "spec/services/completions_decider_spec.rb": 0.01430058479309082, + "spec/views/sign_up/registrations/show.html.slim_spec.rb": 0.07935070991516113, + "spec/services/openid_connect_redirector_spec.rb": 0.026860475540161133, + "spec/services/encrypted_attribute_spec.rb": 0.05307459831237793, + "spec/services/attribute_asserter_spec.rb": 0.9486269950866699, + "spec/views/users/totp_setup/new.html.slim_spec.rb": 0.06574249267578125, + "spec/features/users/sign_in_spec.rb": 29.031192779541016, + "spec/helpers/session_timeout_warning_helper_spec.rb": 0.017833948135375977, + "spec/services/session_encryptor_spec.rb": 0.09229826927185059, + "spec/view_models/account_show_spec.rb": 0.03209733963012695, + "spec/features/saml/sp_initiated_slo_spec.rb": 10.395025253295898, + "spec/controllers/sign_up/registrations_controller_spec.rb": 1.5148699283599854, + "spec/views/two_factor_authentication/personal_key_verification/show.html.slim_spec.rb": 0.21378636360168457, + "spec/controllers/users/verify_account_controller_spec.rb": 0.31940174102783203, + "spec/controllers/openid_connect/configuration_controller_spec.rb": 0.0164182186126709, + "spec/views/layouts/user_mailer.html.slim_spec.rb": 0.17170166969299316, + "spec/requests/edit_user_spec.rb": 3.1227641105651855, + "spec/models/service_provider_spec.rb": 0.07124066352844238, + "spec/presenters/eastern_time_presenter_spec.rb": 0.0027985572814941406, + "spec/svg_spec.rb": 0.22288727760314941, + "spec/services/id_token_builder_spec.rb": 0.3771181106567383, + "spec/services/form_response_spec.rb": 0.013592243194580078, + "spec/mailers/user_mailer_spec.rb": 0.5715782642364502, + "spec/services/service_provider_updater_spec.rb": 0.18952512741088867, + "spec/services/phone_number_capabilities_spec.rb": 0.01145625114440918, + "spec/presenters/openid_connect_configuration_presenter_spec.rb": 0.005454063415527344, + "spec/services/idv/attempter_spec.rb": 0.11740446090698242, + "spec/forms/idv/address_delivery_method_form_spec.rb": 0.007477521896362305, + "spec/decorators/openid_connect_authorize_decorator_spec.rb": 0.001986265182495117, + "spec/services/personal_key_generator_spec.rb": 0.42092061042785645, + "spec/views/sign_up/passwords/new.html.slim_spec.rb": 0.1482555866241455, + "spec/services/vendor_validator_result_storage_spec.rb": 0.008229255676269531, + "spec/forms/openid_connect_logout_form_spec.rb": 0.48787736892700195, + "spec/services/idv/upcase_vendor_env_vars_spec.rb": 0.0036935806274414062, + "spec/features/session/timeout_spec.rb": 0.11951327323913574, + "spec/lib/aws/ses_spec.rb": 0.04176664352416992, + "spec/views/forgot_password/show.html.slim_spec.rb": 0.06267023086547852, + "spec/controllers/verify_controller_spec.rb": 0.5437729358673096, + "spec/controllers/users/personal_keys_controller_spec.rb": 0.33968162536621094, + "spec/forms/update_user_password_form_spec.rb": 1.1650919914245605, + "spec/helpers/application_helper_spec.rb": 0.016112089157104492, + "spec/features/accessibility/visitor_pages_spec.rb": 1.6866345405578613, + "spec/controllers/users/sessions_controller_spec.rb": 1.6692674160003662, + "spec/features/openid_connect/openid_connect_spec.rb": 23.491727113723755, + "spec/features/visitors/email_confirmation_spec.rb": 3.6632091999053955, + "spec/forms/openid_connect_token_form_spec.rb": 1.3590340614318848, + "spec/services/analytics_spec.rb": 0.07381153106689453, + "spec/features/twilio/voice_spec.rb": 0.4620943069458008, + "spec/services/pii/attributes_spec.rb": 0.04794144630432129, + "spec/services/uri_service_spec.rb": 0.007406711578369141, + "spec/lib/no_retry_jobs_spec.rb": 0.0044939517974853516, + "spec/models/user_spec.rb": 0.8011445999145508, + "spec/views/sign_up/personal_keys/show.html.slim_spec.rb": 0.20781421661376953, + "spec/views/devise/passwords/edit.html.slim_spec.rb": 0.09890985488891602, + "spec/services/idv/usps_mail_spec.rb": 0.12160229682922363, + "spec/requests/redirects_spec.rb": 0.012571096420288086, + "spec/controllers/sign_up/emails_controller_spec.rb": 0.035849809646606445, + "spec/controllers/users/emails_controller_spec.rb": 1.2075717449188232, + "spec/features/visitors/resend_email_confirmation_spec.rb": 0.9050788879394531, + "spec/controllers/application_controller_spec.rb": 3.827324628829956, + "spec/jobs/idv/phone_job_spec.rb": 0.013605833053588867, + "spec/requests/rack_attack_spec.rb": 1.3559315204620361, + "spec/views/accounts/_nav_auth.html.slim_spec.rb": 0.06580567359924316, + "spec/forms/reset_password_form_spec.rb": 1.0504989624023438, + "spec/forms/resend_email_confirmation_form_spec.rb": 0.08575987815856934, + "spec/views/shared/_address.html.slim_spec.rb": 0.0058324337005615234, + "spec/features/saml/loa1/account_creation_spec.rb": 0.9248929023742676, + "spec/features/users/sign_out_spec.rb": 0.146531343460083, + "spec/presenters/openid_connect_user_info_presenter_spec.rb": 0.10673284530639648, + "spec/services/access_token_verifier_spec.rb": 0.02900862693786621, + "spec/controllers/sign_up/passwords_controller_spec.rb": 0.09626197814941406, + "spec/services/request_key_manager_spec.rb": 0.0029060840606689453, + "spec/controllers/openid_connect/token_controller_spec.rb": 0.23048782348632812, + "spec/views/two_factor_authentication/shared/max_login_attempts_reached.html.erb_spec.rb": 0.009509801864624023, + "spec/features/users/sign_up_spec.rb": 14.372114419937134, + "spec/controllers/verify/phone_controller_spec.rb": 0.498152494430542, + "spec/features/visitors/phone_confirmation_spec.rb": 2.1987712383270264, + "spec/decorators/identity_decorator_spec.rb": 0.05558371543884277, + "spec/forms/otp_verification_form_spec.rb": 0.048433542251586914, + "spec/models/service_provider_request_spec.rb": 0.007283687591552734, + "spec/models/agency_spec.rb": 0.00803375244140625, + "spec/lib/encrypted_sidekiq_redis_spec.rb": 0.5486271381378174, + "spec/views/accounts/show.html.slim_spec.rb": 0.7933259010314941, + "spec/models/null_service_provider_spec.rb": 0.01456761360168457, + "spec/requests/headers_spec.rb": 0.28769707679748535, + "spec/features/account_history_spec.rb": 0.23647356033325195, + "spec/services/request_password_reset_spec.rb": 0.07263851165771484, + "spec/views/shared/_nav_lite.html.slim_spec.rb": 0.013051509857177734, + "spec/controllers/users/verify_profile_phone_controller_spec.rb": 0.20832157135009766, + "spec/services/active_profile_encryptor_spec.rb": 0.08881402015686035, + "spec/services/email_confirmation_token_validator_spec.rb": 0.04590320587158203, + "spec/controllers/verify/review_controller_spec.rb": 1.2974956035614014, + "spec/views/verify/_hardfail4.html.slim_spec.rb": 0.014304637908935547, + "spec/features/users/verify_profile_spec.rb": 1.8267571926116943, + "spec/services/phone_formatter_spec.rb": 0.009664297103881836, + "spec/lib/yaml_normalizer_spec.rb": 0.011390447616577148, + "spec/lib/sidekiq_logging_formatter_spec.rb": 0.004674196243286133, + "spec/controllers/sign_up/personal_keys_controller_spec.rb": 0.20636963844299316, + "spec/forms/register_user_email_form_spec.rb": 0.43862366676330566, + "spec/controllers/concerns/idv_step_concern_spec.rb": 0.5154056549072266, + "spec/services/pii/password_encryptor_spec.rb": 0.05716443061828613, + "spec/controllers/two_factor_authentication/personal_key_verification_controller_spec.rb": 0.4607572555541992, + "spec/controllers/health/workers_controller_spec.rb": 0.019969701766967773, + "spec/controllers/users_controller_spec.rb": 0.16727972030639648, + "spec/presenters/two_factor_auth_code/phone_delivery_presenter_spec.rb": 0.0354161262512207, + "spec/services/store_sp_metadata_in_session_spec.rb": 0.011577844619750977, + "spec/views/users/passwords/edit.html.slim_spec.rb": 0.10503268241882324, + "spec/presenters/fully_signed_in_modal_presenter_spec.rb": 0.004964113235473633, + "spec/lib/worker_health_checker_spec.rb": 0.03744196891784668, + "spec/requests/constrained_route_spec.rb": 0.5973808765411377, + "spec/services/pii/nist_encryption_spec.rb": 0.07571291923522949, + "spec/views/devise/passwords/new.html.slim_spec.rb": 0.07136082649230957, + "spec/views/sign_up/email_resend/new.html.slim_spec.rb": 0.018687009811401367, + "spec/services/usps_exporter_spec.rb": 0.26345014572143555, + "spec/features/idv/phone_spec.rb": 3.646635055541992, + "spec/lib/feature_management_spec.rb": 0.05365872383117676, + "spec/services/database_health_checker_spec.rb": 0.005364656448364258, + "spec/forms/otp_delivery_selection_form_spec.rb": 0.13983416557312012, + "spec/requests/openid_connect_cors_spec.rb": 0.18818378448486328, + "spec/views/reactivate_account/index.html.slim_spec.rb": 0.03570151329040527, + "spec/controllers/users/two_factor_authentication_setup_controller_spec.rb": 0.21714544296264648, + "spec/presenters/verify/otp_delivery_method_presenter_spec.rb": 0.0020706653594970703, + "spec/jobs/idv/profile_job_spec.rb": 0.024827003479003906, + "spec/services/pii/re_encryptor_spec.rb": 0.17586326599121094, + "spec/features/idv/phone_input_spec.rb": 6.822763681411743, + "spec/forms/password_reset_email_form_spec.rb": 0.06865596771240234, + "spec/models/usps_confirmation_spec.rb": 0.021256208419799805, + "spec/services/parse_controller_from_referer_spec.rb": 0.0043718814849853516, + "spec/models/agency_identity_spec.rb": 0.014325380325317383, + "spec/presenters/utc_time_presenter_spec.rb": 0.0020895004272460938, + "spec/features/visitors/set_password_spec.rb": 3.7771031856536865, + "spec/services/saml_request_validator_spec.rb": 0.02106499671936035, + "spec/features/accessibility/user_pages_spec.rb": 9.998186588287354, + "spec/controllers/users/phones_controller_spec.rb": 0.355557918548584, + "spec/features/saml/loa3_sso_spec.rb": 7.666532516479492, + "spec/services/usps_confirmation_maker_spec.rb": 0.15815019607543945, + "spec/services/agency_identity_linker_spec.rb": 0.3466763496398926, + "spec/controllers/verify/come_back_later_controller_spec.rb": 0.04933810234069824, + "spec/lib/i18n_override_spec.rb": 0.1465606689453125, + "spec/views/layouts/application.html.slim_spec.rb": 2.633995771408081, + "spec/controllers/voice/otp_controller_spec.rb": 3.8286359310150146, + "spec/services/idv/profile_maker_spec.rb": 0.05594325065612793, + "spec/decorators/session_decorator_spec.rb": 0.014308929443359375, + "spec/services/idv/submit_idv_job_spec.rb": 0.026703357696533203, + "spec/features/idv/usps_verification_spec.rb": 6.51273250579834, + "spec/services/idv/vendor_result_spec.rb": 0.008759260177612305, + "spec/features/users/user_profile_spec.rb": 8.698765993118286, + "spec/models/usps_confirmation_code_spec.rb": 0.23659920692443848, + "spec/services/pii/attribute_spec.rb": 0.0040187835693359375, + "spec/controllers/two_factor_authentication/totp_verification_controller_spec.rb": 0.7024211883544922, + "spec/controllers/forgot_password_controller_spec.rb": 0.01839613914489746, + "spec/views/sign_up/completions/show.html.slim_spec.rb": 0.12941575050354004, + "spec/helpers/locale_helper_spec.rb": 0.004418373107910156, + "spec/services/encrypted_key_maker_spec.rb": 0.08191466331481934, + "spec/models/authorization_spec.rb": 0.23125696182250977, + "spec/features/saml/idp_initiated_slo_spec.rb": 4.8129682540893555, + "spec/services/basic_auth_url_spec.rb": 0.005556344985961914, + "spec/forms/totp_setup_form_spec.rb": 0.05111050605773926, + "spec/lib/headers_filter_spec.rb": 0.0026068687438964844, + "spec/controllers/accounts_controller_spec.rb": 0.20701837539672852, + "spec/services/user_access_key_spec.rb": 0.23146915435791016, + "spec/views/verify/usps/index.html.slim_spec.rb": 0.034633636474609375, + "spec/routing/id_verification_routing_spec.rb": 5.364393949508667, + "spec/controllers/sign_up/completions_controller_spec.rb": 0.417513370513916, + "spec/jobs/sms_otp_sender_job_spec.rb": 0.012154102325439453, + "spec/views/shared/_flashes.html.slim_spec.rb": 0.014259576797485352, + "spec/decorators/service_provider_session_decorator_spec.rb": 0.03135848045349121, + "spec/view_models/forgot_password_show_spec.rb": 0.0011701583862304688, + "spec/views/sign_up/registrations/new.html.slim_spec.rb": 0.05396413803100586, + "spec/services/openid_connect_attribute_scoper_spec.rb": 0.014560937881469727, + "spec/lib/config_validator_spec.rb": 0.003065347671508789, + "spec/services/identity_linker_spec.rb": 0.20231914520263672, + "spec/controllers/concerns/user_session_context_spec.rb": 0.008753299713134766, + "spec/services/file_encryptor_spec.rb": 0.05483579635620117, + "spec/services/key_rotator/hmac_fingerprinter_spec.rb": 0.16741085052490234, + "spec/features/idv/sp_requested_attributes_spec.rb": 7.313967704772949, + "spec/requests/page_not_found_spec.rb": 0.04629039764404297, + "spec/services/secure_headers_whitelister_spec.rb": 0.0025877952575683594, + "spec/jobs/voice_otp_sender_job_spec.rb": 0.4395930767059326 } diff --git a/spec/features/accessibility/idv_pages_spec.rb b/spec/features/accessibility/idv_pages_spec.rb index 5897fc58fb6..d789fd1dadc 100644 --- a/spec/features/accessibility/idv_pages_spec.rb +++ b/spec/features/accessibility/idv_pages_spec.rb @@ -48,7 +48,6 @@ fill_out_idv_form_ok click_button t('forms.buttons.continue') click_idv_address_choose_phone - fill_out_phone_form_ok(user.phone) click_button t('forms.buttons.continue') expect(current_path).to eq verify_review_path @@ -61,7 +60,6 @@ fill_out_idv_form_ok click_idv_continue click_idv_address_choose_phone - fill_out_phone_form_ok(user.phone) click_idv_continue fill_in :user_password, with: Features::SessionHelper::VALID_PASSWORD click_continue diff --git a/spec/features/idv/flow_spec.rb b/spec/features/idv/flow_spec.rb index 004ee2a276e..96275ffdf0f 100644 --- a/spec/features/idv/flow_spec.rb +++ b/spec/features/idv/flow_spec.rb @@ -133,8 +133,8 @@ end scenario 'phone step is re-entrant', :js do - phone = '+1 (555) 555-5000' - different_phone = '+1 (777) 777-7000' + phone = '(555) 555-5000' + different_phone = '(777) 777-7000' user = sign_in_and_2fa_user visit verify_session_path @@ -147,7 +147,7 @@ click_link t('forms.two_factor.try_again') - expect(page.find('#idv_phone_form_phone').value).to eq(phone) + expect(page.find('#idv_phone_form_phone').value).to eq("+1 #{phone}") expect(current_path).to eq(verify_phone_path) fill_out_phone_form_ok(different_phone) diff --git a/spec/features/idv/interrupted_session_spec.rb b/spec/features/idv/interrupted_session_spec.rb deleted file mode 100644 index 2955eda075b..00000000000 --- a/spec/features/idv/interrupted_session_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'rails_helper' - -feature 'Interrupted IdV session' do - include IdvHelper - - describe 'Closing the browser while on the first form', js: true, idv_job: true do - before do - sign_in_and_2fa_user - visit verify_session_path - end - - context 'when the alert is dismissed' do - it 'does not display an alert when submitting the form' do - # dismiss the alert that appears when the user closes the browser window - # dismiss means the user clicked on "Stay on Page" - page.driver.browser.dismiss_confirm do - page.driver.close_window(page.driver.current_window_handle) - end - - fill_out_idv_form_ok - click_button t('forms.buttons.continue') - - expect(page).to have_content(t('idv.messages.select_verification_form.phone_message')) - end - end - end -end diff --git a/spec/features/saml/loa1_sso_spec.rb b/spec/features/saml/loa1_sso_spec.rb index 9f47c239058..087eb5f24f5 100644 --- a/spec/features/saml/loa1_sso_spec.rb +++ b/spec/features/saml/loa1_sso_spec.rb @@ -81,7 +81,7 @@ it 'user can view and confirm personal key during sign up', :js do allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true) user = create(:user, :with_phone) - code = '1234' + code = 'ABC1-DEF2-GHI3-JKL4' stub_personal_key(user: user, code: code) loa1_sp_session diff --git a/spec/features/two_factor_authentication/sign_in_spec.rb b/spec/features/two_factor_authentication/sign_in_spec.rb index 593cea03382..42cd9427898 100644 --- a/spec/features/two_factor_authentication/sign_in_spec.rb +++ b/spec/features/two_factor_authentication/sign_in_spec.rb @@ -136,7 +136,7 @@ fill_in 'Phone', with: '5376' select 'Morocco +212', from: 'International code' - expect(find('#user_phone_form_phone').value).to eq '+212 5376' + expect(find('#user_phone_form_phone').value).to eq '+212 (537) 6' fill_in 'Phone', with: '54354' select 'Japan +81', from: 'International code' @@ -264,16 +264,14 @@ def submit_prefilled_otp_code context 'user enters OTP incorrectly 3 times', js: true do it 'locks the user out and leaves user on the page during entire lockout period' do - allow(Figaro.env).to receive(:session_check_frequency).and_return('0') - allow(Figaro.env).to receive(:session_check_delay).and_return('0') lockout_period = Figaro.env.lockout_period_in_minutes.to_i.minutes five_minute_countdown_regex = /4:5\d/ user = create(:user, :signed_up) - sign_in_before_2fa(user) + sign_in_user(user) 3.times do - fill_in('code', with: 'bad-code') + fill_in('code', with: '000000') click_button t('forms.buttons.submit.default') end @@ -284,21 +282,20 @@ def submit_prefilled_otp_code UpdateUser.new( user: user, attributes: { - second_factor_locked_at: Time.zone.now - (lockout_period + 1.second), + second_factor_locked_at: Time.zone.now - (lockout_period + 1.minute), } ).call - sign_in_before_2fa(user) + sign_in_user(user) + fill_in('code', with: user.reload.direct_otp) click_button t('forms.buttons.submit.default') - expect(current_path).to eq account_path + expect(page).to have_current_path account_path end end context 'user requests an OTP too many times within `findtime` minutes', js: true do it 'locks the user out and leaves user on the page during entire lockout period' do - allow(Figaro.env).to receive(:session_check_frequency).and_return('0') - allow(Figaro.env).to receive(:session_check_delay).and_return('0') lockout_period = Figaro.env.lockout_period_in_minutes.to_i.minutes five_minute_countdown_regex = /4:5\d/ diff --git a/spec/features/users/regenerate_personal_key_spec.rb b/spec/features/users/regenerate_personal_key_spec.rb index 3f9de1dd6a4..0503a0367a1 100644 --- a/spec/features/users/regenerate_personal_key_spec.rb +++ b/spec/features/users/regenerate_personal_key_spec.rb @@ -79,6 +79,7 @@ let(:accordion_control_selector) { generate_class_selector('accordion-header-controls') } it 'prompts the user to enter their personal key to confirm they have it' do + Capybara.current_session.current_window.resize_to(2560, 1600) sign_in_and_2fa_user click_button t('account.links.regenerate_personal_key') @@ -92,9 +93,8 @@ expect_confirmation_modal_to_appear_with_first_code_field_in_focus - press_shift_tab - - expect_continue_button_to_be_in_focus + expected_button_order = %w[Back Continue] + expect(all(:button).map(&:text).reject(&:empty?)).to eq expected_button_order click_back_button @@ -119,10 +119,8 @@ click_acknowledge_personal_key expect_confirmation_modal_to_appear_with_first_code_field_in_focus - - press_tab - - expect_continue_button_to_be_in_focus + expected_button_order = %w[Continue Back] + expect(all(:button).map(&:text).reject(&:empty?)).to eq expected_button_order click_back_button @@ -175,22 +173,6 @@ def expect_confirmation_modal_to_appear_with_first_code_field_in_focus expect(page.evaluate_script('document.activeElement.name')).to eq 'personal_key' end -def press_shift_tab - body_element = page.find('body') - body_element.send_keys %i[shift tab] -end - -def press_tab - body_element = page.find('body') - body_element.send_keys %i[tab] -end - -def expect_continue_button_to_be_in_focus - expect(page.evaluate_script('document.activeElement.innerText')).to eq( - t('forms.buttons.continue') - ) -end - def click_back_button click_on t('forms.buttons.back') end diff --git a/spec/features/users/sign_in_spec.rb b/spec/features/users/sign_in_spec.rb index 19dbd7add0d..0cdcd607470 100644 --- a/spec/features/users/sign_in_spec.rb +++ b/spec/features/users/sign_in_spec.rb @@ -60,7 +60,7 @@ scenario 'user can see and use password visibility toggle', js: true do visit new_user_session_path - find('#pw-toggle-0', visible: false).trigger('click') + find('.checkbox').click expect(page).to have_css('input.password[type="text"]') end @@ -99,10 +99,6 @@ scenario 'user sees warning before session times out' do expect(page).to have_css('#session-timeout-msg') - request_headers = page.driver.network_traffic.flat_map(&:headers).uniq - ajax_headers = { 'name' => 'X-Requested-With', 'value' => 'XMLHttpRequest' } - - expect(request_headers).to include ajax_headers time1 = page.text[/14:5[0-9]/] expect(page).to have_content(time1) sleep(1) @@ -111,7 +107,7 @@ end scenario 'user can continue browsing' do - find_link(t('notices.timeout_warning.signed_in.continue')).trigger('click') + find_link(t('notices.timeout_warning.signed_in.continue')).click expect(current_path).to eq account_path end diff --git a/spec/features/users/user_edit_spec.rb b/spec/features/users/user_edit_spec.rb index d8efcf9620a..f0144141869 100644 --- a/spec/features/users/user_edit_spec.rb +++ b/spec/features/users/user_edit_spec.rb @@ -9,11 +9,11 @@ visit manage_email_path end - scenario 'user sees error message if form is submitted without email', :js, idv_job: true do + scenario 'user is not able to submit form without entering an email' do fill_in 'Email', with: '' click_button 'Update' - expect(page).to have_content t('valid_email.validations.email.invalid') + expect(page).to have_current_path manage_email_path end end @@ -44,7 +44,7 @@ fill_in 'Phone', with: '5376' select 'Morocco +212', from: 'International code' - expect(find('#user_phone_form_phone').value).to eq '+212 5376' + expect(find('#user_phone_form_phone').value).to eq '+212 (537) 6' fill_in 'Phone', with: '54354' select 'Japan +81', from: 'International code' diff --git a/spec/features/users/user_profile_spec.rb b/spec/features/users/user_profile_spec.rb index e45973cffe0..8ddd02bb273 100644 --- a/spec/features/users/user_profile_spec.rb +++ b/spec/features/users/user_profile_spec.rb @@ -90,7 +90,7 @@ fill_in 'update_user_password_form_password', with: 'this is a great sentence' expect(page).to have_content 'Great' - find('#pw-toggle-0', visible: false).trigger('click') + find('.checkbox').click expect(page).to_not have_css('input.password[type="password"]') expect(page).to have_css('input.password[type="text"]') diff --git a/spec/features/visitors/i18n_spec.rb b/spec/features/visitors/i18n_spec.rb index bd4f88e8ac7..f6de7dbb04a 100644 --- a/spec/features/visitors/i18n_spec.rb +++ b/spec/features/visitors/i18n_spec.rb @@ -52,15 +52,19 @@ it 'allows user to manually toggle language from dropdown menu', js: true do visit root_path - within(:css, '.i18n-desktop-dropdown', visible: false) do - find_link(t('i18n.locale.es'), visible: false).trigger('click') + using_wait_time(5) do + within(:css, '.i18n-desktop-toggle') do + click_link t('i18n.language', locale: 'en') + click_link t('i18n.locale.es') + end end expect(page).to have_content t('headings.sign_in_without_sp', locale: 'es') expect(page).to have_content t('i18n.language', locale: 'es') - within(:css, '.i18n-desktop-dropdown', visible: false) do - find_link(t('i18n.locale.en'), visible: false).trigger('click') + within(:css, '.i18n-desktop-toggle') do + click_link t('i18n.language', locale: 'es') + click_link t('i18n.locale.en') end expect(page).to have_content t('headings.sign_in_without_sp', locale: 'en') diff --git a/spec/features/visitors/set_password_spec.rb b/spec/features/visitors/set_password_spec.rb index a1351249566..5233ede5563 100644 --- a/spec/features/visitors/set_password_spec.rb +++ b/spec/features/visitors/set_password_spec.rb @@ -65,7 +65,7 @@ expect(page).to have_css('input.password[type="password"]') - find('#pw-toggle-0', visible: false).trigger('click') + find('.checkbox').click expect(page).to_not have_css('input.password[type="password"]') expect(page).to have_css('input.password[type="text"]') diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index 0c048dde27d..45a81eb4d12 100644 --- a/spec/support/capybara.rb +++ b/spec/support/capybara.rb @@ -1,9 +1,21 @@ require 'capybara/rspec' require 'capybara-screenshot/rspec' -require 'capybara/poltergeist' require 'rack_session_access/capybara' +require "selenium/webdriver" + +Capybara.register_driver :headless_chrome do |app| + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: { args: %w[headless disable-gpu] } + ) + + Capybara::Selenium::Driver.new app, + browser: :chrome, + desired_capabilities: capabilities +end + +Capybara.javascript_driver = :headless_chrome +Chromedriver.set_version '2.37' -Capybara.javascript_driver = :poltergeist Capybara.default_max_wait_time = 5 Capybara::Screenshot.autosave_on_failure = false Capybara.asset_host = ENV['RAILS_ASSET_HOST'] || 'http://localhost:3000' diff --git a/spec/support/features/idv_helper.rb b/spec/support/features/idv_helper.rb index 76f5f59aa93..7b3ff9a0f97 100644 --- a/spec/support/features/idv_helper.rb +++ b/spec/support/features/idv_helper.rb @@ -94,11 +94,9 @@ def click_idv_address_choose_usps end def choose_idv_otp_delivery_method_sms - page.find( - 'label', - text: t('devise.two_factor_authentication.otp_delivery_preference.sms') - ).click - click_on t('idv.buttons.send_confirmation_code') + using_wait_time(5) do + click_on t('idv.buttons.send_confirmation_code') + end end def choose_idv_otp_delivery_method_voice @@ -123,7 +121,6 @@ def complete_idv_profile_ok(user, password = user_password) fill_out_idv_form_ok click_idv_continue click_idv_address_choose_phone - fill_out_phone_form_ok(user.phone) click_idv_continue fill_in 'Password', with: password click_continue diff --git a/spec/support/idv_examples/cancel_at_idv_step.rb b/spec/support/idv_examples/cancel_at_idv_step.rb index 1f5548b374e..30e9fe992a8 100644 --- a/spec/support/idv_examples/cancel_at_idv_step.rb +++ b/spec/support/idv_examples/cancel_at_idv_step.rb @@ -40,7 +40,7 @@ expect(page).to have_content(t('idv.cancel.modal_header')) # Clicking return to account takes us to the account page - page.find_button(t('idv.buttons.cancel')).trigger('click') + click_button t('idv.buttons.cancel') expect(page).to have_content(t('headings.account.login_info')) expect(current_path).to eq(account_path) end From 3f8c2a0b0e59e84b8082481b81a20fb693b120b8 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 13 Apr 2018 16:11:14 -0400 Subject: [PATCH 17/39] Update webpacker from 3.2.2 to 3.4.3 --- .circleci/config.yml | 7 +++---- Gemfile | 2 +- Gemfile.lock | 8 ++++---- bin/webpack | 16 +++++----------- bin/webpack-dev-server | 16 +++++----------- package.json | 2 +- spec/features/idv/flow_spec.rb | 2 +- yarn.lock | 28 ++++++++++++++++------------ 8 files changed, 36 insertions(+), 45 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7bc0f716e4b..4603c981f9a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,6 @@ jobs: RAILS_ENV: test CC_TEST_REPORTER_ID: faecd27e9aed532634b3f4d3e251542d7de9457cfca96a94208a63270ef9b42e COVERAGE: true - PATH: "/opt/yarn/yarn-v1.5.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images @@ -45,13 +44,13 @@ jobs: - restore-cache: keys: - - identity-idp-yarn-{{ checksum "yarn.lock" }} - - identity-idp-yarn- + - v1-identity-idp-yarn-{{ checksum "yarn.lock" }} + - v1-identity-idp-yarn- - run: name: Install Yarn command: yarn install --cache-folder ~/.cache/yarn - save-cache: - key: identity-idp-yarn-{{ checksum "yarn.lock" }} + key: v1-identity-idp-yarn-{{ checksum "yarn.lock" }} paths: - ~/.cache/yarn - run: diff --git a/Gemfile b/Gemfile index fe3ad92ec7e..46950f6d909 100644 --- a/Gemfile +++ b/Gemfile @@ -56,7 +56,7 @@ gem 'two_factor_authentication' gem 'typhoeus' gem 'uglifier', '~> 3.2' gem 'valid_email' -gem 'webpacker', '~> 3.2.2' +gem 'webpacker', '~> 3.4' gem 'whenever', require: false gem 'xml-simple' gem 'xmlenc', '~> 0.6.4' diff --git a/Gemfile.lock b/Gemfile.lock index b8e2843259f..2a0a6d1e4ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -201,7 +201,7 @@ GEM connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.3) + crass (1.0.4) css_parser (1.6.0) addressable daemons (1.2.4) @@ -409,7 +409,7 @@ GEM rack (>= 1.2.0) rack-protection (2.0.1) rack - rack-proxy (0.6.3) + rack-proxy (0.6.4) rack rack-test (1.0.0) rack (>= 1.0, < 3) @@ -636,7 +636,7 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff - webpacker (3.2.2) + webpacker (3.4.3) activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) @@ -755,7 +755,7 @@ DEPENDENCIES uglifier (~> 3.2) valid_email webmock - webpacker (~> 3.2.2) + webpacker (~> 3.4) whenever xml-simple xmlenc (~> 0.6.4) diff --git a/bin/webpack b/bin/webpack index 52adc07a215..0869ad277ef 100755 --- a/bin/webpack +++ b/bin/webpack @@ -1,15 +1,7 @@ #!/usr/bin/env ruby -# frozen_string_literal: true -# -# This file was generated by Bundler. -# -# The application 'webpack' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -bundle_binstub = File.expand_path("../bundle", __FILE__) -load(bundle_binstub) if File.file?(bundle_binstub) +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= ENV["NODE_ENV"] || "development" require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", @@ -18,4 +10,6 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", require "rubygems" require "bundler/setup" -load Gem.bin_path("webpacker", "webpack") +require "webpacker" +require "webpacker/webpack_runner" +Webpacker::WebpackRunner.run(ARGV) diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server index 03911f2e7a8..251f65e8e3b 100755 --- a/bin/webpack-dev-server +++ b/bin/webpack-dev-server @@ -1,15 +1,7 @@ #!/usr/bin/env ruby -# frozen_string_literal: true -# -# This file was generated by Bundler. -# -# The application 'webpack-dev-server' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -bundle_binstub = File.expand_path("../bundle", __FILE__) -load(bundle_binstub) if File.file?(bundle_binstub) +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= ENV["NODE_ENV"] || "development" require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", @@ -18,4 +10,6 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", require "rubygems" require "bundler/setup" -load Gem.bin_path("webpacker", "webpack-dev-server") +require "webpacker" +require "webpacker/dev_server_runner" +Webpacker::DevServerRunner.run(ARGV) diff --git a/package.json b/package.json index ec156ff9877..73099cf2f0f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "true" }, "dependencies": { - "@rails/webpacker": "^3.2.2", + "@rails/webpacker": "^3.4.3", "basscss-sass": "^3.0.0", "classlist.js": "^1.1.20150312", "clipboard": "^1.6.1", diff --git a/spec/features/idv/flow_spec.rb b/spec/features/idv/flow_spec.rb index 02114fc0d29..5a1b49a4201 100644 --- a/spec/features/idv/flow_spec.rb +++ b/spec/features/idv/flow_spec.rb @@ -65,7 +65,7 @@ # failure reloads the form and shows warning modal expect(current_path).to eq verify_session_result_path - expect(page).to have_css('.modal-warning', text: t('idv.modal.sessions.heading')) + expect(find('.modal-warning').text).to match t('idv.modal.sessions.heading') click_button t('idv.modal.button.warning') fill_out_idv_form_ok diff --git a/yarn.lock b/yarn.lock index f47a71ee6df..d97b240c92c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,9 +2,9 @@ # yarn lockfile v1 -"@rails/webpacker@^3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-3.2.2.tgz#6d60e1cf729dc2ccc52053c9b6b8d30c9a48a297" +"@rails/webpacker@^3.4.3": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-3.4.3.tgz#496a5d49bea8856db20b212d2727a4b43b281dd9" dependencies: babel-core "^6.26.0" babel-loader "^7.1.2" @@ -14,14 +14,14 @@ babel-polyfill "^6.26.0" babel-preset-env "^1.6.1" case-sensitive-paths-webpack-plugin "^2.1.1" - compression-webpack-plugin "^1.1.6" + compression-webpack-plugin "^1.1.10" css-loader "^0.28.9" extract-text-webpack-plugin "^3.0.2" file-loader "^1.1.6" glob "^7.1.2" js-yaml "^3.10.0" node-sass "^4.7.2" - path-complete-extname "^0.1.0" + path-complete-extname "^1.0.0" postcss-cssnext "^3.1.0" postcss-import "^11.0.0" postcss-loader "^2.1.0" @@ -1611,13 +1611,13 @@ compressible@~2.0.11: dependencies: mime-db ">= 1.30.0 < 2" -compression-webpack-plugin@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-1.1.6.tgz#450808fe143b4c5216a14f0c315c47bec3d83cec" +compression-webpack-plugin@^1.1.10: + version "1.1.11" + resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-1.1.11.tgz#8384c7a6ead1d2e2efb190bdfcdcf35878ed8266" dependencies: - async "^2.4.1" cacache "^10.0.1" find-cache-dir "^1.0.0" + neo-async "^2.5.0" serialize-javascript "^1.4.0" webpack-sources "^1.0.1" @@ -4139,6 +4139,10 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +neo-async@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee" + node-forge@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300" @@ -4487,9 +4491,9 @@ path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" -path-complete-extname@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-0.1.0.tgz#c454702669f31452f8193aa6168915fa31692f4a" +path-complete-extname@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-1.0.0.tgz#f889985dc91000c815515c0bfed06c5acda0752b" path-dirname@^1.0.0: version "1.0.2" From a58cf1634f946e719a796a539a4d355156b6a550 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Fri, 20 Apr 2018 08:23:02 -0500 Subject: [PATCH 18/39] LG-140 Add ability to remember a phone for 2FA (#2063) **Why**: So that users who log in repeatedly do not need to continually 2FA **How**: Store an encrypted cookie that contains the user's ID and the time the cookie was created. When the user goes to 2FA, check for that cookie, and skip 2FA if it exists. --- .../concerns/remember_device_concern.rb | 33 ++++++ .../concerns/two_factor_authenticatable.rb | 4 + app/controllers/users/sessions_controller.rb | 2 + .../two_factor_authentication_controller.rb | 2 + .../generic_delivery_presenter.rb | 6 +- app/services/remember_device_cookie.rb | 51 ++++++++ .../otp_verification/show.html.slim | 8 +- config/application.yml.example | 3 + config/initializers/figaro.rb | 1 + config/locales/forms/en.yml | 1 + config/locales/forms/es.yml | 1 + config/locales/forms/fr.yml | 1 + .../otp_verification_controller_spec.rb | 81 +++++++++++++ .../remember_device_spec.rb | 112 ++++++++++++++++++ .../two_factor_authentication/sign_in_spec.rb | 1 + spec/services/remember_device_cookie_spec.rb | 90 ++++++++++++++ .../shared_examples/remember_device.rb | 81 +++++++++++++ 17 files changed, 476 insertions(+), 2 deletions(-) create mode 100644 app/controllers/concerns/remember_device_concern.rb create mode 100644 app/services/remember_device_cookie.rb create mode 100644 spec/features/two_factor_authentication/remember_device_spec.rb create mode 100644 spec/services/remember_device_cookie_spec.rb create mode 100644 spec/support/shared_examples/remember_device.rb diff --git a/app/controllers/concerns/remember_device_concern.rb b/app/controllers/concerns/remember_device_concern.rb new file mode 100644 index 00000000000..3bc01e76930 --- /dev/null +++ b/app/controllers/concerns/remember_device_concern.rb @@ -0,0 +1,33 @@ +module RememberDeviceConcern + extend ActiveSupport::Concern + + def save_remember_device_preference + return if idv_context? + return unless params[:remember_device] == 'true' + cookies.encrypted[:remember_device] = { + value: RememberDeviceCookie.new(user_id: current_user.id, created_at: Time.zone.now).to_json, + expires: remember_device_cookie_expiration, + } + end + + def check_remember_device_preference + return unless authentication_context? + return if remember_device_cookie.nil? + return unless remember_device_cookie.valid_for_user?(current_user) + handle_valid_otp + end + + def remember_device_cookie + remember_device_cookie_contents = cookies.encrypted[:remember_device] + return if remember_device_cookie_contents.blank? + @remember_device_cookie ||= RememberDeviceCookie.from_json( + remember_device_cookie_contents + ) + end + + private + + def remember_device_cookie_expiration + Figaro.env.remember_device_expiration_days.to_i.days.from_now + end +end diff --git a/app/controllers/concerns/two_factor_authenticatable.rb b/app/controllers/concerns/two_factor_authenticatable.rb index 97cb5f4a0f8..7ea73e5ee32 100644 --- a/app/controllers/concerns/two_factor_authenticatable.rb +++ b/app/controllers/concerns/two_factor_authenticatable.rb @@ -1,5 +1,6 @@ module TwoFactorAuthenticatable extend ActiveSupport::Concern + include RememberDeviceConcern include SecureHeadersConcern included do @@ -74,6 +75,7 @@ def handle_valid_otp elsif idv_or_confirmation_context? || profile_context? handle_valid_otp_for_confirmation_context end + save_remember_device_preference redirect_to after_otp_verification_confirmation_url reset_otp_session_data @@ -241,6 +243,7 @@ def phone_view_data reenter_phone_number_path: reenter_phone_number_path, unconfirmed_phone: unconfirmed_phone?, totp_enabled: current_user.totp_enabled?, + remember_device_available: !idv_context?, }.merge(generic_data) end # rubocop:enable MethodLength @@ -249,6 +252,7 @@ def authenticator_view_data { two_factor_authentication_method: two_factor_authentication_method, user_email: current_user.email, + remember_device_available: false, }.merge(generic_data) end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 168b040e2e3..76980a87ff0 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -1,12 +1,14 @@ module Users class SessionsController < Devise::SessionsController include ::ActionView::Helpers::DateHelper + include SecureHeadersConcern rescue_from ActionController::InvalidAuthenticityToken, with: :redirect_to_signin skip_before_action :session_expires_at, only: [:active] skip_before_action :require_no_authentication, only: [:new] before_action :check_user_needs_redirect, only: [:new] + before_action :apply_secure_headers_override, only: [:new] def new analytics.track_event( diff --git a/app/controllers/users/two_factor_authentication_controller.rb b/app/controllers/users/two_factor_authentication_controller.rb index 58dafe8d070..10ca5e35c09 100644 --- a/app/controllers/users/two_factor_authentication_controller.rb +++ b/app/controllers/users/two_factor_authentication_controller.rb @@ -2,6 +2,8 @@ module Users class TwoFactorAuthenticationController < ApplicationController include TwoFactorAuthenticatable + before_action :check_remember_device_preference + def show if current_user.totp_enabled? redirect_to login_two_factor_authenticator_url diff --git a/app/presenters/two_factor_auth_code/generic_delivery_presenter.rb b/app/presenters/two_factor_auth_code/generic_delivery_presenter.rb index 4c1b231e45d..610fbb4d044 100644 --- a/app/presenters/two_factor_auth_code/generic_delivery_presenter.rb +++ b/app/presenters/two_factor_auth_code/generic_delivery_presenter.rb @@ -4,7 +4,7 @@ class GenericDeliveryPresenter include ActionView::Helpers::TranslationHelper include Rails.application.routes.url_helpers - attr_reader :code_value + attr_reader :code_value, :remember_device_available def initialize(data:, view:) data.each do |key, value| @@ -40,6 +40,10 @@ def reauthn_hidden_field_partial end end + def remember_device_available? + remember_device_available + end + private attr_reader :personal_key_unavailable, :view, :reauthn diff --git a/app/services/remember_device_cookie.rb b/app/services/remember_device_cookie.rb new file mode 100644 index 00000000000..93c0ff1f08d --- /dev/null +++ b/app/services/remember_device_cookie.rb @@ -0,0 +1,51 @@ +class RememberDeviceCookie + COOKIE_ROLE = 'remember_me'.freeze + + attr_reader :user_id, :created_at + + def initialize(user_id:, created_at:) + @user_id = user_id + @created_at = created_at + end + + def self.from_json(json) + parsed_json = JSON.parse(json) + check_cookie_role(parsed_json) + new( + user_id: parsed_json['user_id'], + created_at: Time.zone.parse(parsed_json['created_at']) + ) + end + + private_class_method def self.check_cookie_role(parsed_json) + role = parsed_json['role'] + return if role == COOKIE_ROLE + raise "RememberDeviceCookie role '#{role}' did not match '#{COOKIE_ROLE}'" + end + + def to_json + { + user_id: user_id, + created_at: created_at.iso8601, + role: COOKIE_ROLE, + entropy: SecureRandom.base64(32), + }.to_json + end + + def valid_for_user?(user) + return false if user.id != user_id + return false if user_has_changed_phone?(user) + return false if expired? + true + end + + private + + def expired? + created_at < Figaro.env.remember_device_expiration_days.to_i.days.ago + end + + def user_has_changed_phone?(user) + user.phone_confirmed_at.to_i > created_at.to_i + end +end diff --git a/app/views/two_factor_authentication/otp_verification/show.html.slim b/app/views/two_factor_authentication/otp_verification/show.html.slim index 6861e415f34..cc206d8b409 100644 --- a/app/views/two_factor_authentication/otp_verification/show.html.slim +++ b/app/views/two_factor_authentication/otp_verification/show.html.slim @@ -9,12 +9,18 @@ p == @presenter.phone_number_message = label_tag 'code', \ t('simple_form.required.html') + t('forms.two_factor.code'), \ class: 'block bold' - .col-12.sm-col-5.mb4.sm-mb0.sm-mr-20p.inline-block + .col-12.sm-col-5.mb2.sm-mb0.sm-mr-20p.inline-block = text_field_tag(:code, '', value: @presenter.code_value, required: true, autofocus: true, pattern: '[0-9]*', class: 'col-12 field monospace mfa', 'aria-describedby': 'code-instructs', maxlength: Devise.direct_otp_length, autocomplete: 'off', type: 'tel') = submit_tag t('forms.buttons.submit.default'), class: 'btn btn-primary align-top' + - if @presenter.remember_device_available? + .border.col-9.rounded-lg.mt2 + = check_box_tag 'remember_device', true, false, class: 'my2 ml2 mr1' + = label_tag 'remember_device', + t('forms.messages.remember_device', duration: Figaro.env.remember_device_expiration_days), + class: 'blue' = render 'shared/fallback_links', presenter: @presenter = render 'shared/cancel', link: @presenter.cancel_link diff --git a/config/application.yml.example b/config/application.yml.example index d118ed1c8e7..6529c9f3d7d 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -128,6 +128,7 @@ development: reauthn_window: '120' redis_url: 'redis://localhost:6379/0' redis_throttle_url: 'redis://localhost:6379/1' + remember_device_expiration_days: '30' requests_per_ip_limit: '300' requests_per_ip_period: '300' requests_per_ip_track_only_mode: 'false' @@ -217,6 +218,7 @@ production: reauthn_window: '120' redis_url: 'redis://redis.login.gov.internal:6379' redis_throttle_url: 'redis://redis.login.gov.internal:6379/1' + remember_device_expiration_days: '30' requests_per_ip_limit: '300' requests_per_ip_period: '300' requests_per_ip_track_only_mode: 'true' @@ -307,6 +309,7 @@ test: reauthn_window: '120' redis_url: 'redis://localhost:6379/0' redis_throttle_url: 'redis://localhost:6379/1' + remember_device_expiration_days: '30' requests_per_ip_limit: '4' requests_per_ip_period: '60' requests_per_ip_track_only_mode: 'false' diff --git a/config/initializers/figaro.rb b/config/initializers/figaro.rb index 09bb8913569..3d648127799 100644 --- a/config/initializers/figaro.rb +++ b/config/initializers/figaro.rb @@ -39,6 +39,7 @@ 'requests_per_ip_limit', 'requests_per_ip_period', 'requests_per_ip_track_only_mode', + 'remember_device_expiration_days', 'saml_passphrase', 'scrypt_cost', 'secret_key_base', diff --git a/config/locales/forms/en.yml b/config/locales/forms/en.yml index 0e58a272dca..e3ed8257175 100644 --- a/config/locales/forms/en.yml +++ b/config/locales/forms/en.yml @@ -17,6 +17,7 @@ en: show_hdr: Create a strong password messages: current_address: You should be be able to receive mail at this address. + remember_device: Remember this device for %{duration} days passwords: edit: buttons: diff --git a/config/locales/forms/es.yml b/config/locales/forms/es.yml index 264d58d60bc..2a37fbcacd1 100644 --- a/config/locales/forms/es.yml +++ b/config/locales/forms/es.yml @@ -17,6 +17,7 @@ es: show_hdr: Crear una contrase帽a segura messages: current_address: Deber铆a poder recibir correo en esta direcci贸n. + remember_device: NOT TRANSLATED YET passwords: edit: buttons: diff --git a/config/locales/forms/fr.yml b/config/locales/forms/fr.yml index 7d4eb2298e5..a6fbaa9e070 100644 --- a/config/locales/forms/fr.yml +++ b/config/locales/forms/fr.yml @@ -18,6 +18,7 @@ fr: messages: current_address: Vous devriez 锚tre en mesure de recevoir du courrier 脿 cette adresse. + remember_device: NOT TRANSLATED YET passwords: edit: buttons: diff --git a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb index e47d48f2666..3ab52e3d4e0 100644 --- a/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb +++ b/spec/controllers/two_factor_authentication/otp_verification_controller_spec.rb @@ -182,6 +182,39 @@ otp_delivery_preference: 'sms', } end + + context 'with remember_device in the params' do + it 'saves an encrypted cookie' do + remember_device_cookie = instance_double(RememberDeviceCookie) + allow(remember_device_cookie).to receive(:to_json).and_return('asdf1234') + allow(RememberDeviceCookie).to receive(:new).and_return(remember_device_cookie) + + post( + :create, + params: { + code: subject.current_user.direct_otp, + otp_delivery_preference: 'sms', + remember_device: 'true', + } + ) + + expect(cookies.encrypted[:remember_device]).to eq('asdf1234') + end + end + + context 'without remember_device in the params' do + it 'does not save an encrypted cookie' do + post( + :create, + params: { + code: subject.current_user.direct_otp, + otp_delivery_preference: 'sms', + } + ) + + expect(cookies[:remember_device]).to be_nil + end + end end context 'when the user lockout period expires' do @@ -359,6 +392,39 @@ end end end + + context 'with remember_device in the params' do + it 'saves an encrypted cookie' do + remember_device_cookie = instance_double(RememberDeviceCookie) + allow(remember_device_cookie).to receive(:to_json).and_return('asdf1234') + allow(RememberDeviceCookie).to receive(:new).and_return(remember_device_cookie) + + post( + :create, + params: { + code: subject.current_user.direct_otp, + otp_delivery_preference: 'sms', + remember_device: 'true', + } + ) + + expect(cookies.encrypted[:remember_device]).to eq('asdf1234') + end + end + + context 'without remember_device in the params' do + it 'does not save an encrypted cookie' do + post( + :create, + params: { + code: subject.current_user.direct_otp, + otp_delivery_preference: 'sms', + } + ) + + expect(cookies[:remember_device]).to be_nil + end + end end context 'idv phone confirmation' do @@ -474,6 +540,21 @@ with(Analytics::MULTI_FACTOR_AUTH, properties) end end + + context 'with remember_device in the params' do + it 'ignores the param and does not save an encrypted cookie' do + post( + :create, + params: { + code: subject.current_user.direct_otp, + otp_delivery_preference: 'sms', + remember_device: 'true', + } + ) + + expect(cookies[:remember_device]).to be_nil + end + end end end end diff --git a/spec/features/two_factor_authentication/remember_device_spec.rb b/spec/features/two_factor_authentication/remember_device_spec.rb new file mode 100644 index 00000000000..40db5c8feba --- /dev/null +++ b/spec/features/two_factor_authentication/remember_device_spec.rb @@ -0,0 +1,112 @@ +require 'rails_helper' + +feature 'Remembering a 2FA device' do + include IdvHelper + + before do + allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true) + allow(SmsOtpSenderJob).to receive(:perform_now) + allow(Figaro.env).to receive(:otp_delivery_blocklist_maxretry).and_return('1000') + end + + let(:user) { user_with_2fa } + + context 'sign in' do + def remember_device_and_sign_out_user + user = user_with_2fa + sign_in_user(user) + check :remember_device + click_submit_default + first(:link, t('links.sign_out')).click + user + end + + it_behaves_like 'remember device' + end + + context 'sign up' do + def remember_device_and_sign_out_user + user = sign_up_and_set_password + user.password = Features::SessionHelper::VALID_PASSWORD + fill_in :user_phone_form_phone, with: '5551231234' + click_send_security_code + check :remember_device + click_submit_default + click_acknowledge_personal_key + first(:link, t('links.sign_out')).click + user + end + + it_behaves_like 'remember device' + end + + context 'update phone number' do + def remember_device_and_sign_out_user + user = user_with_2fa + sign_in_and_2fa_user(user) + visit manage_phone_path + fill_in 'user_phone_form_phone', with: '5552347193' + click_button t('forms.buttons.submit.confirm_change') + check :remember_device + click_submit_default + first(:link, t('links.sign_out')).click + user + end + + it_behaves_like 'remember device' + + it 'requires the user to confirm the new phone number' do + user = user_with_2fa + sign_in_user(user) + check :remember_device + click_submit_default + + visit manage_phone_path + fill_in 'user_phone_form_phone', with: '5552347193' + click_button t('forms.buttons.submit.confirm_change') + + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + end + end + + context 'identity verification', :idv_job do + let(:user) { user_with_2fa } + + before do + sign_in_user(user) + check :remember_device + click_submit_default + visit verify_session_path + fill_out_idv_form_ok + click_idv_continue + click_idv_address_choose_phone + fill_out_phone_form_ok('5551603829') + click_idv_continue + choose_idv_otp_delivery_method_sms + end + + it 'requires 2FA and does not offer the option to remember device' do + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + expect(page).to_not have_content( + t('forms.messages.remember_device', duration: Figaro.env.remember_device_expiration_days!) + ) + end + end + + context 'totp' do + let(:user) do + user = build(:user, :signed_up, password: 'super strong password') + @secret = user.generate_totp_secret + UpdateUser.new(user: user, attributes: { otp_secret_key: @secret }).call + user + end + + it 'does not offer the option to remember device' do + sign_in_user(user) + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :authenticator)) + expect(page).to_not have_content( + t('forms.messages.remember_device', duration: Figaro.env.remember_device_expiration_days!) + ) + end + end +end diff --git a/spec/features/two_factor_authentication/sign_in_spec.rb b/spec/features/two_factor_authentication/sign_in_spec.rb index 42cd9427898..3c202afd25c 100644 --- a/spec/features/two_factor_authentication/sign_in_spec.rb +++ b/spec/features/two_factor_authentication/sign_in_spec.rb @@ -203,6 +203,7 @@ def submit_2fa_setup_form_with_valid_phone_and_choose_phone_call_delivery expect(current_path).to eq login_two_factor_path(otp_delivery_preference: 'sms') + check 'remember_device' submit_prefilled_otp_code expect(current_path).to eq account_path diff --git a/spec/services/remember_device_cookie_spec.rb b/spec/services/remember_device_cookie_spec.rb new file mode 100644 index 00000000000..26f7617e519 --- /dev/null +++ b/spec/services/remember_device_cookie_spec.rb @@ -0,0 +1,90 @@ +require 'rails_helper' + +describe RememberDeviceCookie do + let(:phone_confirmed_at) { 90.days.ago } + let(:user) { create(:user, phone_confirmed_at: phone_confirmed_at) } + let(:created_at) { Time.zone.now } + + subject { described_class.new(user_id: user.id, created_at: created_at) } + + describe '.from_json(json)' do + it 'should parse a JSON string' do + json = { + user_id: 1, + created_at: created_at.iso8601, + role: 'remember_me', + entropy: '123abc', + }.to_json + subject = described_class.from_json(json) + + expect(subject.user_id).to eq(1) + expect(subject.created_at.iso8601).to eq(created_at.iso8601) + end + + it 'should raise an error if the role in the JSON string is not "remember_me"' do + json = { + user_id: 1, + created_at: created_at.iso8601, + role: 'something_else', + entropy: '123abc', + }.to_json + + expect { described_class.from_json(json) }.to raise_error( + RuntimeError, + "RememberDeviceCookie role 'something_else' did not match 'remember_me'" + ) + end + + it 'should raise an error if the role in the JSON string is missing' do + json = { + user_id: 1, + created_at: created_at.iso8601, + entropy: '123abc', + }.to_json + + expect { described_class.from_json(json) }.to raise_error( + RuntimeError, + "RememberDeviceCookie role '' did not match 'remember_me'" + ) + end + end + + describe '#to_json' do + it 'should render a JSON string' do + json = subject.to_json + parsed_json = JSON.parse(json) + + expect(parsed_json['user_id']).to eq(user.id) + expect(parsed_json['created_at']).to eq(created_at.iso8601) + expect(parsed_json['role']).to eq('remember_me') + expect(parsed_json['entropy']).to_not be_nil + end + end + + describe '#valid_for_user?(user)' do + context 'when the token is valid' do + it { expect(subject.valid_for_user?(user)).to eq(true) } + end + + context 'when the token is expired' do + let(:created_at) { (Figaro.env.remember_device_expiration_days.to_i + 1).days.ago } + + it { expect(subject.valid_for_user?(user)).to eq(false) } + end + + context 'when the token does not refer to the current user' do + it 'returns false' do + other_user = create(:user, phone_confirmed_at: 90.days.ago) + + expect(subject.valid_for_user?(other_user)).to eq(false) + end + end + + context 'when the user has changed their phone since creating the token' do + let(:created_at) { 5.days.ago } + let(:phone_confirmed_at) { 4.days.ago } + + it { expect(subject.valid_for_user?(user)).to eq(false) } + end + end +end diff --git a/spec/support/shared_examples/remember_device.rb b/spec/support/shared_examples/remember_device.rb new file mode 100644 index 00000000000..8d1a65da850 --- /dev/null +++ b/spec/support/shared_examples/remember_device.rb @@ -0,0 +1,81 @@ +shared_examples 'remember device' do + it 'does not require 2FA on sign in' do + user = remember_device_and_sign_out_user + sign_in_user(user) + + expect(current_path).to eq(account_path) + end + + it 'requires 2FA on sign in after expiration' do + user = remember_device_and_sign_out_user + + Timecop.travel (Figaro.env.remember_device_expiration_days.to_i + 1).days.from_now do + sign_in_user(user) + + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + end + end + + it 'requires 2FA on sign in after phone number is changed' do + user = remember_device_and_sign_out_user + + sign_in_user(user) + visit manage_phone_path + fill_in 'user_phone_form_phone', with: '5551230000' + click_button t('forms.buttons.submit.confirm_change') + click_submit_default + first(:link, t('links.sign_out')).click + + sign_in_user(user) + + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + end + + it 'requires 2FA on sign in for another user' do + first_user = remember_device_and_sign_out_user + + second_user = user_with_2fa + + # Sign in as second user and expect otp confirmation + sign_in_user(second_user) + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + + # Setup remember device as second user + check :remember_device + click_submit_default + + # Sign out second user + first(:link, t('links.sign_out')).click + + # Sign in as first user again and expect otp confirmation + sign_in_user(first_user) + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + end + + it 'redirects to an SP from the sign in page' do + oidc_url = openid_connect_authorize_url( + client_id: 'urn:gov:gsa:openidconnect:sp:server', + response_type: 'code', + acr_values: Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF, + scope: 'openid email', + redirect_uri: 'http://localhost:7654/auth/result', + state: SecureRandom.hex, + nonce: SecureRandom.hex + ) + user = remember_device_and_sign_out_user + + IdentityLinker.new( + user, 'urn:gov:gsa:openidconnect:sp:server' + ).link_identity(verified_attributes: %w[email]) + + visit oidc_url + click_link t('links.sign_in') + + expect(page.response_headers['Content-Security-Policy']). + to(include('form-action \'self\' http://localhost:7654')) + + sign_in_user(user) + + expect(current_url).to start_with('http://localhost:7654/auth/result') + end +end From 0b7cf1017b14d7dbc86da074082f912213425df8 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Fri, 20 Apr 2018 08:49:45 -0500 Subject: [PATCH 19/39] Add tests for USPS idv steps (#2107) **Why**: So we can collect tests related to each part of the verification by letter process in once place --- .../steps/usps_otp_verification_step_spec.rb | 9 +++ spec/features/idv/steps/usps_step_spec.rb | 66 +++++++++++++++++++ spec/features/idv/usps_verification_spec.rb | 12 ---- spec/support/features/idv_step_helper.rb | 22 ++++++- ...ation.rb => usps_otp_verification_step.rb} | 2 +- .../usps_verification_selection.rb | 46 ------------- 6 files changed, 95 insertions(+), 62 deletions(-) create mode 100644 spec/features/idv/steps/usps_otp_verification_step_spec.rb create mode 100644 spec/features/idv/steps/usps_step_spec.rb delete mode 100644 spec/features/idv/usps_verification_spec.rb rename spec/support/idv_examples/{usps_verification.rb => usps_otp_verification_step.rb} (97%) diff --git a/spec/features/idv/steps/usps_otp_verification_step_spec.rb b/spec/features/idv/steps/usps_otp_verification_step_spec.rb new file mode 100644 index 00000000000..7bc561a637f --- /dev/null +++ b/spec/features/idv/steps/usps_otp_verification_step_spec.rb @@ -0,0 +1,9 @@ +require 'rails_helper' + +feature 'idv usps otp verification step' do + include IdvStepHelper + + it_behaves_like 'usps otp verfication step' + it_behaves_like 'usps otp verfication step', :oidc + it_behaves_like 'usps otp verfication step', :saml +end diff --git a/spec/features/idv/steps/usps_step_spec.rb b/spec/features/idv/steps/usps_step_spec.rb new file mode 100644 index 00000000000..f062ff96313 --- /dev/null +++ b/spec/features/idv/steps/usps_step_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +feature 'idv usps step', :idv_job do + include IdvStepHelper + + it 'redirects to the review step when the user chooses to verify by letter' do + start_idv_from_sp + complete_idv_steps_before_usps_step + click_on t('idv.buttons.mail.send') + + expect(page).to have_content(t('idv.titles.session.review')) + expect(page).to have_current_path(verify_review_path) + end + + it 'redirects to the phone step when the user says they cannot receive mail' do + start_idv_from_sp + complete_idv_steps_before_usps_step + + click_on t('idv.messages.usps.bad_address') + + expect(page).to have_content(t('idv.titles.session.phone')) + expect(page).to have_current_path(verify_phone_path) + end + + context 'the user has sent a letter but not verified an OTP' do + let(:user) { user_with_2fa } + + it 'allows the user to resend a letter and redirects to the come back later step' do + complete_idv_and_return_to_usps_step + + expect { click_on t('idv.buttons.mail.resend') }. + to change { UspsConfirmation.count }.from(1).to(2) + expect_user_to_be_unverified(user) + expect(page).to have_content(t('idv.titles.come_back_later')) + expect(page).to have_current_path(verify_come_back_later_path) + end + + def complete_idv_and_return_to_usps_step + start_idv_from_sp + complete_idv_steps_before_usps_step(user) + click_on t('idv.buttons.mail.send') + fill_in 'Password', with: user_password + click_continue + click_acknowledge_personal_key + visit root_path + first(:link, t('links.sign_out')).click + sign_in_live_with_2fa(user) + click_on t('idv.messages.usps.resend') + end + + def expect_user_to_be_unverified(user) + expect(user.events.account_verified.size).to be(0) + expect(user.profiles.count).to eq 1 + + profile = user.profiles.first + + expect(profile.active?).to eq false + expect(profile.deactivation_reason).to eq 'verification_pending' + expect(profile.phone_confirmed).to eq false + end + end + + context 'cancelling IdV' do + # USPS step does not have a cancel button :( + end +end diff --git a/spec/features/idv/usps_verification_spec.rb b/spec/features/idv/usps_verification_spec.rb deleted file mode 100644 index 0815936b27f..00000000000 --- a/spec/features/idv/usps_verification_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'rails_helper' - -feature 'USPS verification' do - include SamlAuthHelper - include IdvHelper - - context 'signing in when profile is pending USPS verification' do - it_behaves_like 'signing in with pending USPS verification' - it_behaves_like 'signing in with pending USPS verification', :saml - it_behaves_like 'signing in with pending USPS verification', :oidc - end -end diff --git a/spec/support/features/idv_step_helper.rb b/spec/support/features/idv_step_helper.rb index d834db73ab9..cf7fa5c557a 100644 --- a/spec/support/features/idv_step_helper.rb +++ b/spec/support/features/idv_step_helper.rb @@ -53,14 +53,30 @@ def complete_idv_steps_before_phone_otp_verification_step(user = user_with_2fa) choose_idv_otp_delivery_method_sms end - def complete_idv_steps_before_review_step(user = user_with_2fa) + def complete_idv_steps_with_phone_before_review_step(user = user_with_2fa) complete_idv_steps_before_phone_step(user) fill_out_phone_form_ok(user.phone) click_idv_continue end - def complete_idv_steps_before_confirmation_step(user = user_with_2fa) - complete_idv_steps_before_review_step(user) + def complete_idv_steps_with_phone_before_confirmation_step(user = user_with_2fa) + complete_idv_steps_with_phone_before_review_step(user) + password = user.password || user_password + fill_in 'Password', with: password + click_continue + end + + alias complete_idv_steps_before_review_step complete_idv_steps_with_phone_before_review_step + alias complete_idv_steps_before_confirmation_step complete_idv_steps_with_phone_before_confirmation_step + + def complete_idv_steps_with_usps_before_review_step(user = user_with_2fa) + complete_idv_steps_before_usps_step(user) + click_on t('idv.buttons.mail.send') + end + + def complete_idv_steps_with_usps_before_confirmation_step(user = user_with_2fa) + complete_idv_steps_with_usps_before_review_step(user) + password = user.password || user_password fill_in 'Password', with: password click_continue end diff --git a/spec/support/idv_examples/usps_verification.rb b/spec/support/idv_examples/usps_otp_verification_step.rb similarity index 97% rename from spec/support/idv_examples/usps_verification.rb rename to spec/support/idv_examples/usps_otp_verification_step.rb index 900bb40c587..b5bdbd5c143 100644 --- a/spec/support/idv_examples/usps_verification.rb +++ b/spec/support/idv_examples/usps_otp_verification_step.rb @@ -1,4 +1,4 @@ -shared_examples 'signing in with pending USPS verification' do |sp| +shared_examples 'usps otp verfication step' do |sp| let(:otp) { 'ABC123' } let(:profile) do create( diff --git a/spec/support/idv_examples/usps_verification_selection.rb b/spec/support/idv_examples/usps_verification_selection.rb index d4050d13281..7c4031081cf 100644 --- a/spec/support/idv_examples/usps_verification_selection.rb +++ b/spec/support/idv_examples/usps_verification_selection.rb @@ -47,50 +47,4 @@ to eq('urn:gov:gsa:openidconnect:sp:server') end end - - describe 'USPS OTP prefilling' do - it 'prefills USPS OTP if the reveal_usps_code feature flag is set', email: true do - visit_idp_from_sp_with_loa3(sp) - register_user - - usps_confirmation_maker = instance_double(UspsConfirmationMaker) - allow(usps_confirmation_maker).to receive(:otp).and_return('123ABC') - allow(usps_confirmation_maker).to receive(:perform) - allow(UspsConfirmationMaker).to receive(:new).and_return(usps_confirmation_maker) - allow(FeatureManagement).to receive(:reveal_usps_code?).and_return(true) - - complete_idv_profile_ok_with_usps - - visit verify_account_path - - expect(page.find('#verify_account_form_otp').value).to eq '123ABC' - end - - it 'does not prefill USPS OTP if the reveal_usps_code feature flag is not set', email: true do - visit_idp_from_sp_with_loa3(sp) - register_user - - usps_confirmation_maker = instance_double(UspsConfirmationMaker) - allow(usps_confirmation_maker).to receive(:otp).and_return('123ABC') - allow(usps_confirmation_maker).to receive(:perform) - allow(UspsConfirmationMaker).to receive(:new).and_return(usps_confirmation_maker) - allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(false) - - complete_idv_profile_ok_with_usps - - visit verify_account_path - - expect(page.find('#verify_account_form_otp').value).to be_nil - end - end - - def complete_idv_profile_ok_with_usps - click_idv_begin - fill_out_idv_form_ok - click_idv_continue - click_idv_address_choose_usps - click_on t('idv.buttons.mail.send') - fill_in :user_password, with: user_password - click_continue - end end From a9d3e58d9bc171c8743ee0f709d6bd6c62d87007 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Thu, 19 Apr 2018 22:32:22 -0400 Subject: [PATCH 20/39] Upgrade Ahoy from 1.6.1 to 2.0.2 --- .reek | 1 + Gemfile | 2 +- Gemfile.lock | 17 +++++++---------- config/initializers/ahoy.rb | 37 ++++++++++++++++++++++++++++++++++--- 4 files changed, 43 insertions(+), 14 deletions(-) diff --git a/.reek b/.reek index 69ebee1f99d..7c0a066a100 100644 --- a/.reek +++ b/.reek @@ -20,6 +20,7 @@ DuplicateMethodCall: FeatureEnvy: exclude: - ActiveJob::Logging::LogSubscriber#json_for + - Ahoy::Store#track_event - Aws::SES::Base#deliver - CustomDeviseFailureApp#build_options - CustomDeviseFailureApp#keys diff --git a/Gemfile b/Gemfile index 46950f6d909..5c4f6c99b9e 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ ruby '~> 2.3.7' gem 'rails', '~> 5.1.3' -gem 'ahoy_matey', '~> 1.6.1' +gem 'ahoy_matey', '~> 2.0' gem 'american_date' gem 'aws-sdk-kms', '~> 1.4' gem 'aws-sdk-ses', '~> 1.6' diff --git a/Gemfile.lock b/Gemfile.lock index 2a0a6d1e4ed..31420ca6e2e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,17 +109,15 @@ GEM addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) aes_key_wrap (1.0.1) - ahoy_matey (1.6.1) + ahoy_matey (2.0.2) addressable browser (~> 2.0) - geocoder - rack-attack (< 6) - railties - referer-parser (>= 0.3.0) + geocoder (>= 1.4.5) + railties (>= 4.2) + referer-parser (>= 0.3) request_store - safely_block (>= 0.1.1) + safely_block (>= 0.2.1) user_agent_parser - uuidtools akami (1.3.1) gyoku (>= 0.4.0) nokogiri @@ -306,7 +304,7 @@ GEM httpi (2.4.2) rack socksify - i18n (1.0.0) + i18n (1.0.1) concurrent-ruby (~> 1.0) i18n-tasks (0.9.21) activesupport (>= 4.0.2) @@ -618,7 +616,6 @@ GEM useragent (0.16.8) uuid (2.3.8) macaddr (~> 1.0) - uuidtools (2.1.5) valid_email (0.1.0) activemodel mail (>= 2.6.1) @@ -666,7 +663,7 @@ PLATFORMS DEPENDENCIES aamva! - ahoy_matey (~> 1.6.1) + ahoy_matey (~> 2.0) american_date aws-sdk-kms (~> 1.4) aws-sdk-ses (~> 1.6) diff --git a/config/initializers/ahoy.rb b/config/initializers/ahoy.rb index 6bf4544d0b6..25a77515846 100644 --- a/config/initializers/ahoy.rb +++ b/config/initializers/ahoy.rb @@ -1,13 +1,44 @@ -Ahoy.mount = false -Ahoy.throttle = false +Ahoy.api = false # Period of inactivity before a new visit is created Ahoy.visit_duration = Figaro.env.session_timeout_in_minutes.to_i.minutes +Ahoy.server_side_visits = false +Ahoy.geocode = false module Ahoy - class Store < Ahoy::Stores::LogStore + class Store < Ahoy::BaseStore + def track_visit(data) + log_visit(data) + end + + def track_event(data) + data[:id] = data.delete(:event_id) + data[:visitor_id] = ahoy.visitor_token + data[:visit_id] = data.delete(:visit_token) + + log_event(data) + end + def exclude? return if FeatureManagement.enable_load_testing_mode? super end + + protected + + def log_visit(data) + visit_logger.info data.to_json + end + + def log_event(data) + event_logger.info data.to_json + end + + def visit_logger + @visit_logger ||= ActiveSupport::Logger.new(Rails.root.join('log', 'visits.log')) + end + + def event_logger + @event_logger ||= ActiveSupport::Logger.new(Rails.root.join('log', 'events.log')) + end end end From 263f435544b6bbb23a466d72391acdc50827de1f Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Thu, 12 Apr 2018 11:27:44 -0400 Subject: [PATCH 21/39] Add temporary mailer **Why**: To facilitate sending a one-time email to some users. --- app/mailers/user_mailer.rb | 4 +++ .../user_mailer/reset_password.html.slim | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 app/views/user_mailer/reset_password.html.slim diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 1f41ae6dee3..9d432473f5b 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -25,4 +25,8 @@ def account_does_not_exist(email, request_id) @sign_up_email_url = sign_up_email_url(request_id: request_id, locale: locale_url_param) mail(to: email, subject: t('user_mailer.account_does_not_exist.subject')) end + + def reset_password(email) + mail(to: email, subject: 'Please reset your password') + end end diff --git a/app/views/user_mailer/reset_password.html.slim b/app/views/user_mailer/reset_password.html.slim new file mode 100644 index 00000000000..aebc044cc3b --- /dev/null +++ b/app/views/user_mailer/reset_password.html.slim @@ -0,0 +1,32 @@ +p.lead == "During a login.gov security review, we found that your account \ + password was the same as your email address. For your security, \ + we have disabled your old password and you must now reset your \ + password. Although we did not see any suspicious activity on your \ + account, we disabled your password to be extra careful." + +p.lead == "To continue using your login.gov account, please reset your password \ + using the link below. Please note that login.gov needs your password to \ + be different from your email address." + +table.button.expanded.large.radius + tbody + tr + td + table + tbody + tr + td + center + = link_to t('mailer.reset_password.link_text'), + forgot_password_url, + target: '_blank', class: 'float-center', align: 'center' + td.expander + +p + = link_to forgot_password_url, forgot_password_url, target: '_blank' + +table.spacer + tbody + tr + td.s10 height="10px" + |   From d95cf6c0ae77f4d679ecc8c12060ad63b6e3dbc2 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Fri, 20 Apr 2018 12:43:53 -0500 Subject: [PATCH 22/39] Add phone otp verification step spec (#2112) **Why**: So we can collect the things we want to test with regards to the phone OTP verification --- spec/features/idv/cancel_idv_step_spec.rb | 6 ---- .../steps/phone_otp_verification_step_spec.rb | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 spec/features/idv/steps/phone_otp_verification_step_spec.rb diff --git a/spec/features/idv/cancel_idv_step_spec.rb b/spec/features/idv/cancel_idv_step_spec.rb index 111caa801cd..2bf1f98aeac 100644 --- a/spec/features/idv/cancel_idv_step_spec.rb +++ b/spec/features/idv/cancel_idv_step_spec.rb @@ -15,12 +15,6 @@ it_behaves_like 'cancel at idv step', :profile, :saml end - context 'phone otp verification step' do - it_behaves_like 'cancel at idv step', :phone_otp_verification - it_behaves_like 'cancel at idv step', :phone_otp_verification, :oidc - it_behaves_like 'cancel at idv step', :phone_otp_verification, :saml - end - xcontext 'usps step' do # USPS step does not have a cancel button :( end diff --git a/spec/features/idv/steps/phone_otp_verification_step_spec.rb b/spec/features/idv/steps/phone_otp_verification_step_spec.rb new file mode 100644 index 00000000000..0cc8c49c19e --- /dev/null +++ b/spec/features/idv/steps/phone_otp_verification_step_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +feature 'phone otp verification step spec', :idv_job do + include IdvStepHelper + + it 'requires the user to enter the correct otp before continuing' do + user = user_with_2fa + + start_idv_from_sp + complete_idv_steps_before_phone_otp_verification_step(user) + + # Attempt to bypass the step + visit verify_review_path + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + + # Enter an incorrect otp + fill_in 'code', with: '000000' + click_submit_default + + expect(page).to have_content(t('devise.two_factor_authentication.invalid_otp')) + expect(current_path).to eq(login_two_factor_path(otp_delivery_preference: :sms)) + + # Enter the correct code + enter_correct_otp_code_for_user(user) + + expect(page).to have_content(t('idv.titles.session.review')) + expect(page).to have_current_path(verify_review_path) + end + + it_behaves_like 'cancel at idv step', :phone_otp_verification + it_behaves_like 'cancel at idv step', :phone_otp_verification, :oidc + it_behaves_like 'cancel at idv step', :phone_otp_verification, :saml +end From 1e1409cab405ba13cb766a3c31dc85475f23e347 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:05:23 -0400 Subject: [PATCH 23/39] Update bummr from 0.2.1 to 0.3.2 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 31420ca6e2e..bc9ad70e314 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -165,7 +165,7 @@ GEM bullet (5.7.5) activesupport (>= 3.0.0) uniform_notifier (~> 1.11.0) - bummr (0.2.1) + bummr (0.3.2) rainbow thor byebug (10.0.0) From c4b48f91e33fc4ef742a36ecd31f05dc6dcd8f9e Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:05:43 -0400 Subject: [PATCH 24/39] Update database_cleaner from 1.6.2 to 1.7.0 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index bc9ad70e314..6d257ce1f91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -203,7 +203,7 @@ GEM css_parser (1.6.0) addressable daemons (1.2.4) - database_cleaner (1.6.2) + database_cleaner (1.7.0) debug_inspector (0.0.3) derailed (0.1.0) derailed_benchmarks From 9964c1f79353ec97539a8400bb7c2593911a2159 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:05:57 -0400 Subject: [PATCH 25/39] Update email_spec from 2.1.1 to 2.2.0 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6d257ce1f91..3b723847b1f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -234,10 +234,10 @@ GEM easy_translate (0.5.1) thread thread_safe - email_spec (2.1.1) + email_spec (2.2.0) htmlentities (~> 4.3.3) launchy (~> 2.1) - mail (~> 2.6) + mail (~> 2.7) encryptor (3.0.0) equalizer (0.0.11) errbase (0.1.0) From dcab92abddaaf6573f8f988dc2d8b5bab52217d3 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:06:13 -0400 Subject: [PATCH 26/39] Update fasterer from 0.4.0 to 0.4.1 --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3b723847b1f..80ce12a707f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -259,9 +259,9 @@ GEM i18n (>= 0.7) faraday (0.14.0) multipart-post (>= 1.2, < 3) - fasterer (0.4.0) + fasterer (0.4.1) colorize (~> 0.7) - ruby_parser (~> 3.9) + ruby_parser (~> 3.11.0) ffi (1.9.23) ffi-compiler (1.0.1) ffi (>= 1.0.0) @@ -503,7 +503,7 @@ GEM ruby-saml (1.7.2) nokogiri (>= 1.5.10) ruby_dep (1.5.0) - ruby_parser (3.10.1) + ruby_parser (3.11.0) sexp_processor (~> 4.9) rubyzip (1.2.1) safe_yaml (1.0.4) @@ -536,7 +536,7 @@ GEM selenium-webdriver (3.11.0) childprocess (~> 0.5) rubyzip (~> 1.2) - sexp_processor (4.10.0) + sexp_processor (4.11.0) shellany (0.0.1) shoulda-matchers (3.1.2) activesupport (>= 4.0.0) From aa00e6226cc84ab16254537a26c8580d83e89180 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:06:27 -0400 Subject: [PATCH 27/39] Update httparty from 0.16.1 to 0.16.2 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 80ce12a707f..433a8b2418d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,7 +299,7 @@ GEM hiredis (0.6.1) htmlentities (4.3.4) http_accept_language (2.1.1) - httparty (0.16.1) + httparty (0.16.2) multi_xml (>= 0.5.2) httpi (2.4.2) rack From 65051574b1dd2d5185c15c208865d6d8852fedfb Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:06:42 -0400 Subject: [PATCH 28/39] Update overcommit from 0.44.0 to 0.45.0 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 433a8b2418d..eb617cc0f01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -371,7 +371,7 @@ GEM nenv (~> 0.1) shellany (~> 0.0) orm_adapter (0.5.0) - overcommit (0.44.0) + overcommit (0.45.0) childprocess (~> 0.6, >= 0.6.3) iniparse (~> 1.4) parallel (1.12.1) From 4f9aa32b8c033a873d6ebf895b6009f62dfe678a Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:06:58 -0400 Subject: [PATCH 29/39] Update phonelib from 0.6.19 to 0.6.21 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index eb617cc0f01..748375094d1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -378,7 +378,7 @@ GEM parser (2.5.0.5) ast (~> 2.4.0) pg (1.0.0) - phonelib (0.6.19) + phonelib (0.6.21) phony (2.15.44) phony_rails (0.14.6) activesupport (>= 3.0) From 73dcb01618eb2bba0e90d7df007199e592cc22e9 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:07:18 -0400 Subject: [PATCH 30/39] Update rack_session_access from 0.1.1 to 0.2.0 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 748375094d1..99c4b331a01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -412,7 +412,7 @@ GEM rack-test (1.0.0) rack (>= 1.0, < 3) rack-timeout (0.4.2) - rack_session_access (0.1.1) + rack_session_access (0.2.0) builder (>= 2.0.0) rack (>= 1.0.0) rails (5.1.6) From be31440f79b0297ed4b262df4ae02692a4b29a91 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:07:38 -0400 Subject: [PATCH 31/39] Update reek from 4.8.0 to 4.8.1 --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 99c4b331a01..3901bec3612 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -375,7 +375,7 @@ GEM childprocess (~> 0.6, >= 0.6.3) iniparse (~> 1.4) parallel (1.12.1) - parser (2.5.0.5) + parser (2.5.1.0) ast (~> 2.4.0) pg (1.0.0) phonelib (0.6.21) @@ -457,10 +457,10 @@ GEM connection_pool (~> 2.1) redis (>= 3.0, < 5.0) redis (3.3.5) - reek (4.8.0) + reek (4.8.1) codeclimate-engine-rb (~> 0.4.0) parser (>= 2.5.0.0, < 2.6) - rainbow (~> 3.0) + rainbow (>= 2.0, < 4.0) referer-parser (0.3.0) request_store (1.4.1) rack (>= 1.4) From d574c0a0aa3d4524e6b0b9561027f140b27af484 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:07:58 -0400 Subject: [PATCH 32/39] Update sidekiq from 5.1.2 to 5.1.3 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3901bec3612..b5d8fa195da 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -540,7 +540,7 @@ GEM shellany (0.0.1) shoulda-matchers (3.1.2) activesupport (>= 4.0.0) - sidekiq (5.1.2) + sidekiq (5.1.3) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) From 7cee896955f8c8a6c61bb280874667baaacdbce3 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:08:20 -0400 Subject: [PATCH 33/39] Update simple_form from 3.5.1 to 4.0.0 --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b5d8fa195da..050639d68ba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -545,9 +545,9 @@ GEM connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) redis (>= 3.3.5, < 5) - simple_form (3.5.1) - actionpack (> 4, < 5.2) - activemodel (> 4, < 5.2) + simple_form (4.0.0) + actionpack (> 4) + activemodel (> 4) simplecov (0.13.0) docile (~> 1.1.0) json (>= 1.8, < 3) From 40884141aa862d853d1a286d9f0dd00c1b1bd655 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 13:21:20 -0400 Subject: [PATCH 34/39] LG-208 Create user Event when password is changed **Why**: To make it easier to troubleshoot and determine if the user ever changed their password via the account page. --- app/controllers/users/passwords_controller.rb | 1 + .../users/passwords_controller_spec.rb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb index 834b88f67f5..7b5b0e50d03 100644 --- a/app/controllers/users/passwords_controller.rb +++ b/app/controllers/users/passwords_controller.rb @@ -28,6 +28,7 @@ def user_params end def handle_success + create_user_event(:password_changed) bypass_sign_in current_user flash[:personal_key] = @update_user_password_form.personal_key diff --git a/spec/controllers/users/passwords_controller_spec.rb b/spec/controllers/users/passwords_controller_spec.rb index e46fe81bc49..6f8f185d432 100644 --- a/spec/controllers/users/passwords_controller_spec.rb +++ b/spec/controllers/users/passwords_controller_spec.rb @@ -32,6 +32,7 @@ allow(updater).to receive(:submit).and_return(response) personal_key = 'five random words for test' allow(updater).to receive(:personal_key).and_return(personal_key) + allow(controller).to receive(:create_user_event) params = { password: password } patch :update, params: { update_user_password_form: params } @@ -40,6 +41,15 @@ expect(updater).to have_received(:submit) expect(updater).to have_received(:personal_key) end + + it 'creates a user Event for the password change' do + stub_sign_in + + expect(controller).to receive(:create_user_event) + + params = { password: 'salty new password' } + patch :update, params: { update_user_password_form: params } + end end context 'form returns failure' do @@ -62,6 +72,15 @@ with(Analytics::PASSWORD_CHANGED, success: false, errors: errors) expect(response).to render_template(:edit) end + + it 'does not create a password_changed user Event' do + stub_sign_in + + expect(controller).to_not receive(:create_user_event) + + params = { password: 'new' } + patch :update, params: { update_user_password_form: params } + end end end end From 842f87bcb5632cc6c7acf8e9192d953d4a9a5804 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Fri, 20 Apr 2018 15:07:57 -0500 Subject: [PATCH 35/39] Create a profile and verify step spec (#2102) **Why**: So we can test all of the profile step features in one place --- spec/features/idv/cancel_idv_step_spec.rb | 21 -------- spec/features/idv/fail_to_verify_spec.rb | 8 --- spec/features/idv/failed_job_spec.rb | 9 ---- spec/features/idv/max_attempts_spec.rb | 12 ----- spec/features/idv/steps/profile_step_spec.rb | 53 ++++++++++++++++++++ spec/features/idv/steps/verify_step_spec.rb | 24 +++++++++ 6 files changed, 77 insertions(+), 50 deletions(-) delete mode 100644 spec/features/idv/cancel_idv_step_spec.rb delete mode 100644 spec/features/idv/fail_to_verify_spec.rb delete mode 100644 spec/features/idv/failed_job_spec.rb delete mode 100644 spec/features/idv/max_attempts_spec.rb create mode 100644 spec/features/idv/steps/profile_step_spec.rb create mode 100644 spec/features/idv/steps/verify_step_spec.rb diff --git a/spec/features/idv/cancel_idv_step_spec.rb b/spec/features/idv/cancel_idv_step_spec.rb deleted file mode 100644 index 2bf1f98aeac..00000000000 --- a/spec/features/idv/cancel_idv_step_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rails_helper' - -feature 'cancel at IdV step', :idv_job do - include IdvStepHelper - - context 'verify step' do - it_behaves_like 'cancel at idv step', :verify - it_behaves_like 'cancel at idv step', :verify, :oidc - it_behaves_like 'cancel at idv step', :verify, :saml - end - - context 'profile step' do - it_behaves_like 'cancel at idv step', :profile - it_behaves_like 'cancel at idv step', :profile, :oidc - it_behaves_like 'cancel at idv step', :profile, :saml - end - - xcontext 'usps step' do - # USPS step does not have a cancel button :( - end -end diff --git a/spec/features/idv/fail_to_verify_spec.rb b/spec/features/idv/fail_to_verify_spec.rb deleted file mode 100644 index 4e50070940f..00000000000 --- a/spec/features/idv/fail_to_verify_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'rails_helper' - -feature 'fail to verify', :idv_job do - include IdvStepHelper - - it_behaves_like 'fail to verify idv info', :profile - it_behaves_like 'fail to verify idv info', :phone -end diff --git a/spec/features/idv/failed_job_spec.rb b/spec/features/idv/failed_job_spec.rb deleted file mode 100644 index 4f4f8c6b594..00000000000 --- a/spec/features/idv/failed_job_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'rails_helper' - -feature 'IdV session', :idv_job do - include IdvStepHelper - - context 'profile job' do - it_behaves_like 'failed idv job', :profile - end -end diff --git a/spec/features/idv/max_attempts_spec.rb b/spec/features/idv/max_attempts_spec.rb deleted file mode 100644 index c9586947b79..00000000000 --- a/spec/features/idv/max_attempts_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'rails_helper' - -feature 'IdV max attempts', :idv_job, :email do - include IdvStepHelper - include JavascriptDriverHelper - - context 'profile step' do - it_behaves_like 'verification step max attempts', :profile - it_behaves_like 'verification step max attempts', :profile, :oidc - it_behaves_like 'verification step max attempts', :profile, :saml - end -end diff --git a/spec/features/idv/steps/profile_step_spec.rb b/spec/features/idv/steps/profile_step_spec.rb new file mode 100644 index 00000000000..d4cd5e79055 --- /dev/null +++ b/spec/features/idv/steps/profile_step_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +feature 'idv profile step', :idv_job do + include IdvStepHelper + + context 'with valid information' do + it 'requires the user to complete to continue to the address step and is not re-entrant' do + start_idv_from_sp + complete_idv_steps_before_profile_step + + # Try to skip ahead to address step + visit verify_address_path + + # Get redirected to the profile step + expect(page).to have_current_path(verify_session_path) + + # Complete the idv form + fill_out_idv_form_ok + click_idv_continue + + # Expect to be on the address step + expect(page).to have_content(t('idv.titles.select_verification')) + expect(page).to have_current_path(verify_address_path) + + # Attempt to go back to profile step + visit verify_session_path + + # Get redirected to the address step + expect(page).to have_content(t('idv.titles.select_verification')) + expect(page).to have_current_path(verify_address_path) + end + end + + context 'cancelling IdV' do + it_behaves_like 'cancel at idv step', :profile + it_behaves_like 'cancel at idv step', :profile, :oidc + it_behaves_like 'cancel at idv step', :profile, :saml + end + + context "when the user's information cannot be verified" do + it_behaves_like 'fail to verify idv info', :profile + end + + context 'when the IdV background job fails' do + it_behaves_like 'failed idv job', :profile + end + + context 'after the max number of attempts' do + it_behaves_like 'verification step max attempts', :profile + it_behaves_like 'verification step max attempts', :profile, :oidc + it_behaves_like 'verification step max attempts', :profile, :saml + end +end diff --git a/spec/features/idv/steps/verify_step_spec.rb b/spec/features/idv/steps/verify_step_spec.rb new file mode 100644 index 00000000000..a429eaaccc7 --- /dev/null +++ b/spec/features/idv/steps/verify_step_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +feature 'idv verify step' do + include IdvStepHelper + + it 'allows the user to continue to the profile step' do + start_idv_from_sp + complete_idv_steps_before_verify_step + + expect(page).to have_content(t('idv.titles.expectations')) + expect(page).to have_current_path(verify_path) + + click_idv_begin + + expect(page).to have_content(t('idv.titles.sessions')) + expect(page).to have_current_path(verify_session_path) + end + + context 'cancelling idv' do + it_behaves_like 'cancel at idv step', :verify + it_behaves_like 'cancel at idv step', :verify, :oidc + it_behaves_like 'cancel at idv step', :verify, :saml + end +end From 10524a38217bfa91052fae83df0fe17da7406e2e Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Fri, 20 Apr 2018 14:08:41 -0400 Subject: [PATCH 36/39] Update twilio-ruby from 5.7.2 to 5.8.0 --- Gemfile.lock | 4 ++-- ...o_factor_authentication_controller_spec.rb | 23 +++++++++++++------ spec/features/users/sign_up_spec.rb | 4 +++- spec/services/twilio_service_spec.rb | 17 ++++++++++---- spec/support/fake_twilio_error_response.rb | 15 ++++++++++++ 5 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 spec/support/fake_twilio_error_response.rb diff --git a/Gemfile.lock b/Gemfile.lock index 050639d68ba..63a407d0453 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -257,7 +257,7 @@ GEM fakefs (0.13.3) faker (1.8.7) i18n (>= 0.7) - faraday (0.14.0) + faraday (0.15.0) multipart-post (>= 1.2, < 3) fasterer (0.4.1) colorize (~> 0.7) @@ -593,7 +593,7 @@ GEM thread_safe (0.3.6) tilt (2.0.8) timecop (0.9.1) - twilio-ruby (5.7.2) + twilio-ruby (5.8.0) faraday (~> 0.9) jwt (>= 1.5, <= 2.5) nokogiri (>= 1.6, < 2.0) diff --git a/spec/controllers/users/two_factor_authentication_controller_spec.rb b/spec/controllers/users/two_factor_authentication_controller_spec.rb index 07638f618bb..a190a83877c 100644 --- a/spec/controllers/users/two_factor_authentication_controller_spec.rb +++ b/spec/controllers/users/two_factor_authentication_controller_spec.rb @@ -246,7 +246,9 @@ def index end it 'flashes an sms error when twilio responds with an sms error' do - twilio_error = Twilio::REST::RestError.new('', TwilioService::SMS_ERROR_CODE, '400') + twilio_error = Twilio::REST::RestError.new( + '', FakeTwilioErrorResponse.new(TwilioService::SMS_ERROR_CODE) + ) allow(SmsOtpSenderJob).to receive(:perform_now).and_raise(twilio_error) get :send_code, params: { otp_delivery_selection_form: { otp_delivery_preference: 'sms' } } @@ -255,7 +257,9 @@ def index end it 'flashes an invalid error when twilio responds with an invalid error' do - twilio_error = Twilio::REST::RestError.new('', TwilioService::INVALID_ERROR_CODE, '400') + twilio_error = Twilio::REST::RestError.new( + '', FakeTwilioErrorResponse.new(TwilioService::INVALID_ERROR_CODE) + ) allow(SmsOtpSenderJob).to receive(:perform_now).and_raise(twilio_error) get :send_code, params: { otp_delivery_selection_form: { otp_delivery_preference: 'sms' } } @@ -265,7 +269,7 @@ def index it 'flashes an error when twilio responds with an invalid calling area error' do twilio_error = Twilio::REST::RestError.new( - '', TwilioService::INVALID_CALLING_AREA_ERROR_CODE, '400' + '', FakeTwilioErrorResponse.new(TwilioService::INVALID_CALLING_AREA_ERROR_CODE) ) allow(VoiceOtpSenderJob).to receive(:perform_now).and_raise(twilio_error) @@ -277,7 +281,7 @@ def index it 'flashes an error when twilio responds with an invalid voice number' do twilio_error = Twilio::REST::RestError.new( - '', TwilioService::INVALID_VOICE_NUMBER_ERROR_CODE, '400' + '', FakeTwilioErrorResponse.new(TwilioService::INVALID_VOICE_NUMBER_ERROR_CODE) ) allow(VoiceOtpSenderJob).to receive(:perform_now).and_raise(twilio_error) @@ -288,7 +292,9 @@ def index end it 'flashes a failed to send error when twilio responds with an unknown error' do - twilio_error = Twilio::REST::RestError.new('', '', '400') + twilio_error = Twilio::REST::RestError.new( + '', FakeTwilioErrorResponse.new + ) allow(SmsOtpSenderJob).to receive(:perform_now).and_raise(twilio_error) get :send_code, params: { otp_delivery_selection_form: { otp_delivery_preference: 'sms' } } @@ -298,7 +304,9 @@ def index it 'records an analytics event when Twilio responds with an error' do stub_analytics - twilio_error = Twilio::REST::RestError.new('error message', '', '400') + twilio_error = Twilio::REST::RestError.new( + 'error message', FakeTwilioErrorResponse.new + ) allow(SmsOtpSenderJob).to receive(:perform_now).and_raise(twilio_error) analytics_hash = { success: true, @@ -309,12 +317,13 @@ def index country_code: '1', area_code: '202', } + twilio_error = "[HTTP 400] : error message\n\n" expect(@analytics).to receive(:track_event). with(Analytics::OTP_DELIVERY_SELECTION, analytics_hash) expect(@analytics).to receive(:track_event). - with(Analytics::TWILIO_PHONE_VALIDATION_FAILED, error: 'error message', code: '') + with(Analytics::TWILIO_PHONE_VALIDATION_FAILED, error: twilio_error, code: '') get :send_code, params: { otp_delivery_selection_form: { otp_delivery_preference: 'sms' } } end diff --git a/spec/features/users/sign_up_spec.rb b/spec/features/users/sign_up_spec.rb index 0ae1aa993b0..5e18a9f8a67 100644 --- a/spec/features/users/sign_up_spec.rb +++ b/spec/features/users/sign_up_spec.rb @@ -49,7 +49,9 @@ end scenario 'renders an error when twilio api responds with an error' do - twilio_error = Twilio::REST::RestError.new('', TwilioService::SMS_ERROR_CODE, '400') + twilio_error = Twilio::REST::RestError.new( + '', FakeTwilioErrorResponse.new(TwilioService::SMS_ERROR_CODE) + ) allow(SmsOtpSenderJob).to receive(:perform_now).and_raise(twilio_error) sign_up_and_set_password diff --git a/spec/services/twilio_service_spec.rb b/spec/services/twilio_service_spec.rb index 70f2260640e..4902f7b82e9 100644 --- a/spec/services/twilio_service_spec.rb +++ b/spec/services/twilio_service_spec.rb @@ -78,12 +78,17 @@ raw_message = 'Unable to create record: Account not authorized to call +123456789012.' error_code = '21215' status_code = 400 - sanitized_message = 'Unable to create record: Account not authorized to call +12345#######.' + sanitized_message = "[HTTP #{status_code}] #{error_code} : Unable to create record: Account " \ + "not authorized to call +12345#######.\n\n" service = TwilioService.new + raw_error = Twilio::REST::RestError.new( + raw_message, FakeTwilioErrorResponse.new(error_code) + ) + expect(service.send(:client).calls).to receive(:create). - and_raise(Twilio::REST::RestError.new(raw_message, error_code, status_code)) + and_raise(raw_error) expect { service.place_call(to: '+123456789012', url: 'https://twimlet.com') }. to raise_error(Twilio::REST::RestError, sanitized_message) @@ -116,12 +121,16 @@ raw_message = "The 'To' number +1 (888) 555-5555 is not a valid phone number" error_code = '21211' status_code = 400 - sanitized_message = "The 'To' number +1 (888) 5##-#### is not a valid phone number" + sanitized_message = "[HTTP #{status_code}] #{error_code} : The 'To' " \ + "number +1 (888) 5##-#### is not a valid phone number\n\n" service = TwilioService.new + raw_error = Twilio::REST::RestError.new( + raw_message, FakeTwilioErrorResponse.new(error_code) + ) expect(service.send(:client).messages).to receive(:create). - and_raise(Twilio::REST::RestError.new(raw_message, error_code, status_code)) + and_raise(raw_error) expect { service.send_sms(to: '+1 (888) 555-5555', body: 'test') }. to raise_error(Twilio::REST::RestError, sanitized_message) diff --git a/spec/support/fake_twilio_error_response.rb b/spec/support/fake_twilio_error_response.rb new file mode 100644 index 00000000000..11f4a0f0333 --- /dev/null +++ b/spec/support/fake_twilio_error_response.rb @@ -0,0 +1,15 @@ +class FakeTwilioErrorResponse + attr_reader :code + + def initialize(code = '') + @code = code + end + + def status_code + 400 + end + + def body + { 'code' => code } + end +end From 04e3e1f0f099da41008df7428f371ce4d6f9bd21 Mon Sep 17 00:00:00 2001 From: Steve Urciuoli Date: Thu, 12 Apr 2018 16:45:57 -0400 Subject: [PATCH 37/39] LG-207 Update NGA SPs in production **Why**: NGA needs new redirect URIs and return_to_sp URLs **How**: Update service_providers.yml --- app/assets/images/sp-logos/dot.svg | 16 ++++++ certs/sp/dot_portal_prod.crt | 22 ++++++++ certs/sp/nga_geoworks_symphony_prod.crt | 22 ++++++++ certs/sp/nga_mage_prod.crt | 18 ++++++ config/agencies.yml | 6 ++ config/service_providers.yml | 75 +++++++++++++++++++++++-- 6 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 app/assets/images/sp-logos/dot.svg create mode 100644 certs/sp/dot_portal_prod.crt create mode 100644 certs/sp/nga_geoworks_symphony_prod.crt create mode 100644 certs/sp/nga_mage_prod.crt diff --git a/app/assets/images/sp-logos/dot.svg b/app/assets/images/sp-logos/dot.svg new file mode 100644 index 00000000000..5cf159b74a2 --- /dev/null +++ b/app/assets/images/sp-logos/dot.svg @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/certs/sp/dot_portal_prod.crt b/certs/sp/dot_portal_prod.crt new file mode 100644 index 00000000000..d4dd3e0fe24 --- /dev/null +++ b/certs/sp/dot_portal_prod.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDmTCCAoECCQCM4ARJaZKjOjANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMC +VVMxHTAbBgNVBAgMFERpc3RyaWN0IG9mIENvbHVtYmlhMRMwEQYDVQQHDApXYXNo +aW5ndG9uMQwwCgYDVQQKDANET1QxFzAVBgNVBAMMDnBvcnRhbC5kb3QuZ292MSEw +HwYJKoZIhvcNAQkBFhJkb25vdHJlcGx5QGRvdC5nb3YwHhcNMTgwMjI0MjExMTIx +WhcNMTkwNzA5MjExMTIxWjCBkDELMAkGA1UEBhMCVVMxHTAbBgNVBAgMFERpc3Ry +aWN0IG9mIENvbHVtYmlhMRMwEQYDVQQHDApXYXNoaW5ndG9uMRAwDgYDVQQKDAdE +T1QtT1NUMRcwFQYDVQQDDA5wb3J0YWwuZG90LmdvdjEiMCAGCSqGSIb3DQEJARYT +ZG9ub3RyZXBseTJAZG90LmdvdjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAOFbeV3WA3AS6E6r2LBfTvBfrhsn4SDE93gLUCr2JzQgMrn61CGIIAkB96s8 +MjXJiiPFU4MQdIB/+NMojFcv84wwczTtTqZnyJCdgv0DUqqx20fQBzwI1/eBrMDm +kdMbDuV/y3CalLv9RPM7lK6iUmD6kmBsqPus+H2AtKEow0F8F6LTp82URGCDmw6V +srBdxcBs9SVtSOP0EXBgr2rauA6pY9YBc2bjQoDN7qltIRXYIIzAm8BgCYQTTV5F +yk1MII2UBkt0zFP38bh/LuJielQk5ondYndHfU3bq8lk28xfaEMGhb+feVyRTZoA +s4gQrrKY7iqyhbgeD2LIbugWx2sCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHbp4 +Tz2ugcozzpHDpoKzCNMwLB2FXpL15cZTwl7EULtREtjYpbcLxX4SfpFZjviSV6Lw +4q9jGV0VwiaI7WkyI/YDLyn7t21dm5gkYen+WNwUkxLVce5TCn8tDS9joyIO9OeX +w8p2KvZUEa1EPn4pxvy3MOJFDspcmvTs9KWHzZ+QlkSGZXCnW4KxOa2p1nZOkVPV +t9hrARpBTBqI9Xse8VwmwotwCFMZOVU9Z3Ct5WHBKI8WNyb18fcfw/li+3PZE3wc +hCAnrmI48FEjOGo0/gFoKYparJlKA3nP6cX2Ts4nrYpf4tW2Gpb8yshcMb1DQ7S6 +YeXSB0mY2n9zJU0Edg== +-----END CERTIFICATE----- diff --git a/certs/sp/nga_geoworks_symphony_prod.crt b/certs/sp/nga_geoworks_symphony_prod.crt new file mode 100644 index 00000000000..4ada4803f23 --- /dev/null +++ b/certs/sp/nga_geoworks_symphony_prod.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIJAKWUn3z6Kzs4MA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJNQTEQMA4GA1UEBwwHQmVkZm9yZDEOMAwGA1UECgwF +TUlUUkUxIjAgBgNVBAMMGXN5bXBob255Lm5nYS1nZW93b3Jrcy5jb20wHhcNMTgw +NDE4MTgwMTU4WhcNMTkwNDE4MTgwMTU4WjBgMQswCQYDVQQGEwJVUzELMAkGA1UE +CAwCTUExEDAOBgNVBAcMB0JlZGZvcmQxDjAMBgNVBAoMBU1JVFJFMSIwIAYDVQQD +DBlzeW1waG9ueS5uZ2EtZ2Vvd29ya3MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAvvKZ+/f3/WVDNn/DuMR2S4SQYhDtWgx7/4BDRa1iudjTOiQC +20JIdlFOS1Jc3X9Yatdbeds6l1cTQJJ2g24zX9qVds3ceWt65/BvbqQgS5pJnKxg +zS0o5VrI6HEHisxQTlJXscXG9pq7rc7zOxhxIq/JN1QPql+txi2gZon7tP6C98g8 +2QUCm3nV++5nW6sE155vZEdky8ywojijECf6OxbJAE5J8S+H6Xj3KuvqiUHR5CX7 +QJzbDO7cHvG9nqni+Lx31leyEaN3VNXMdm2zq3/Dhiv/8HujzyEU3/FH/LGpzJ9o +BRWAOt5TGllWuYtRfNjUnIND+OkwVhMG0zOi6QIDAQABo1AwTjAdBgNVHQ4EFgQU +HEz4IBNYOm0UdlJpIy5ovZio3L8wHwYDVR0jBBgwFoAUHEz4IBNYOm0UdlJpIy5o +vZio3L8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEACLa0y6g5Ykwf +CSiQPUiV/HR7PbQRW5e6C0pNEiUAoFN1pnXfAZuo0NAjC9Dr1ZsdHh3+kj8F9eQS +b8mkHfyrKJzrRXHS4CjtZoB+B26JBX718exANz9q4Q+/D6iFBZEv9cIcYcd+S18M ++Ju0raW0EOhtCseBsWruqWLlWhis39HLEfMz1jsWbPSxgrHwXFrnJaT6ohYfdND3 +L77Min4+1uj9QTyRb8erOd8LxnX5eq6NEU5oYU1PqymVyzUhx83ceuSPT4gu/zUm +DPI8WEh7NPhiXk68+0knwXqnRf/Y3wHGK/FyB1GVMF+YcPf7xV7te6NCt2F1fqkV +sPmUdzSx1A== +-----END CERTIFICATE----- diff --git a/certs/sp/nga_mage_prod.crt b/certs/sp/nga_mage_prod.crt new file mode 100644 index 00000000000..e4fb9062305 --- /dev/null +++ b/certs/sp/nga_mage_prod.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7jCCAdYCCQCCXfou5HUi8DANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ08xDzANBgNVBAcMBkF1cm9yYTEMMAoGA1UECgwDTkdBMB4X +DTE4MDMyODIxNTQ0MloXDTIzMDMyNzIxNTQ0MlowOTELMAkGA1UEBhMCVVMxCzAJ +BgNVBAgMAkNPMQ8wDQYDVQQHDAZBdXJvcmExDDAKBgNVBAoMA05HQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/iUCQNz6UoizKdHJjGcOxwgKGAkxKj +zmoXkslL/Kr0zXPjRuPDAoPHVaEChf3xekRvZeuyDq7WkszFwFX2qT8Bq7vFyZU2 +AJVRHwBVE+SYc/XugZGHPYq1rPpGgz36zBVofVVuhnigEL4WO3f/5Xu7+iD/lSEV +rNWPKWplzuZ7lhTRY9qdYr2arYXhMXrxMQmiiVT9ivHtrKfLAxNj6AUgWVsFe+Rv ++lUS8Qv+O85YsUpWovYogsTtmqOn3sLW3RuDe//PnU6xUPxnTzj6uv+h4BpF66z7 +0Ytfkx0Zfl+Kla2CymzmK6zOLTRA3h2Du5gOM8ApWzB/OigOeEuGQE0CAwEAATAN +BgkqhkiG9w0BAQUFAAOCAQEAmfVSGHVeRwOToKy83L8+0WYyd8lBLqOjs0cWkJhU +fSRfE+NjSX28hFPCrYv7/w47q8Cvf2t69QHOzl0jLY5nYdsitBf6zwbRlPQq5uus +zcLfRTW4FBEQq5T4peMW/28u50TxhAC5er64wtrCUu9KtgPbKh2YeIEczv6SeIkM ++QCCLnYpx86j0/+yjTrX/grC3MChG+Md4irVbqkDX/UzoqRvBK12CUWAlTCsKu/c +vB93c5wMljKu6xjhacB0lyZi7ne/2Y2wJVrskofIUbjYUaeGDlqpDdYw+uYHvIcX +3g2UIGBYcg3qan+fsTpuND1o5t0M/p7ZhMexVc85ij4FWA== +-----END CERTIFICATE----- diff --git a/config/agencies.yml b/config/agencies.yml index e1b489d2a03..eb989d62eb2 100644 --- a/config/agencies.yml +++ b/config/agencies.yml @@ -9,6 +9,8 @@ test: name: 'RRB' 5: name: 'NGA' + 6: + name: 'DOT' development: 1: @@ -21,6 +23,8 @@ development: name: 'RRB' 5: name: 'NGA' + 6: + name: 'DOT' production: 1: @@ -33,3 +37,5 @@ production: name: 'RRB' 5: name: 'NGA' + 6: + name: 'DOT' diff --git a/config/service_providers.yml b/config/service_providers.yml index a820f0aff50..dea0e9edd10 100644 --- a/config/service_providers.yml +++ b/config/service_providers.yml @@ -501,9 +501,15 @@ production: agency: 'NGA' logo: 'nga.png' cert: 'nga_geoworks_prod' - return_to_sp_url: 'https://geoworks.nga-geoworks.com' + return_to_sp_url: 'https://nga-geoworks.com' redirect_uris: + - 'https://geoworks.geointservices.io/auth/login-gov/callback/loa-1' - 'https://geoworks.nga-geoworks.com/auth/login-gov/callback/loa-1' + - 'https://nga-geoworks.com/auth/login-gov/callback/loa-1' + - 'https://ngageoworks.org/auth/login-gov/callback/loa-1' + - 'https://nga-geoworks.org/auth/login-gov/callback/loa-1' + - 'https://nga-geoworks.io/auth/login-gov/callback/loa-1' + - 'https://ngageoworks.io/auth/login-gov/callback/loa-1' restrict_to_deploy_env: 'prod' # NGA GEOINT Viewer @@ -513,9 +519,14 @@ production: agency: 'NGA' logo: 'nga.png' cert: 'nga_geoint_viewer_prod' - return_to_sp_url: 'https://gv-geoworks.nga-geoworks.com' + return_to_sp_url: 'https://gv.nga-geoworks.com' redirect_uris: - - 'https://gv-geoworks.nga-geoworks.com/protected/callback' + - 'https://gv.geointservices.io/protected/callback' + - 'https://gv.nga-geoworks.com/protected/callback' + - 'https://gv.ngageoworks.org/protected/callback' + - 'https://gv.nga-geoworks.org/protected/callback' + - 'https://gv.nga-geoworks.io/protected/callback' + - 'https://gv.ngageoworks.io/protected/callback' restrict_to_deploy_env: 'prod' # NGA HiPER CLOUD @@ -525,8 +536,64 @@ production: agency: 'NGA' logo: 'nga.png' cert: 'nga_hiper_look_prod' - return_to_sp_url: 'https://hiperlook-ppp.nga-geoworks.com' + return_to_sp_url: 'https://hiperlook.nga-geoworks.com' redirect_uris: - 'https://hiperlook-ppp.nga-geoworks.com/auth_redirect' - 'https://hiperlook-ppp.nga-geoworks.com:443/auth_redirect' + - 'https://hiperlook-ppp.geointservices.io/auth_redirect' + - 'https://hiperlook-ppp.geointservices.io:443/auth_redirect' + - 'https://hiperlook.ngageoworks.org/auth_redirect' + - 'https://hiperlook.ngageoworks.org:443/auth_redirect' + - 'https://hiperlook.nga-geoworks.org/auth_redirect' + - 'https://hiperlook.nga-geoworks.org:443/auth_redirect' + - 'https://hiperlook.nga-geoworks.io/auth_redirect' + - 'https://hiperlook.nga-geoworks.io:443/auth_redirect' + - 'https://hiperlook.ngageoworks.io/auth_redirect' + - 'https://hiperlook.ngageoworks.io:443/auth_redirect' + - 'https://hiperlook.nga-geoworks.com/auth_redirect' + - 'https://hiperlook.nga-geoworks.com:443/auth_redirect' + restrict_to_deploy_env: 'prod' + + # NGA MAGE + 'urn:gov:gsa:openidconnect.profiles:sp:sso:nga:mage': + agency_id: 5 + friendly_name: 'MAGE' + agency: 'NGA' + logo: 'nga.png' + cert: 'nga_mage_prod' + return_to_sp_url: 'https://mage.nga-geoworks.com' + redirect_uris: + - 'https://mage-geoworks.geointservices.io/auth/login-gov/callback/loa-1' + - 'https://mage.nga-geoworks.com/auth/login-gov/callback/loa-1' + - 'https://mage.ngageoworks.org/auth/login-gov/callback/loa-1' + - 'https://mage.nga-geoworks.org/auth/login-gov/callback/loa-1' + - 'https://mage.nga-geoworks.io/auth/login-gov/callback/loa-1' + - 'https://mage.ngageoworks.io/auth/login-gov/callback/loa-1' + restrict_to_deploy_env: 'prod' + + # DOT + 'urn:gov:gsa:openidconnect.profiles:sp:sso:dot:login': + agency_id: 6 + friendly_name: 'DOT Portal' + agency: 'DOT' + logo: 'dot.svg' + cert: 'dot_portal_prod' + redirect_uris: + - 'https://acquia-stage.portal.dot.gov/openid-connect/dot_login' + - 'https://portal.dot.gov/' + restrict_to_deploy_env: 'prod' + + # NGA GEOWorks Symphony + 'urn:gov:gsa:openidconnect.profiles:sp:sso:mitre:symphony': + agency_id: 5 + friendly_name: 'GEOWorks/Symphony' + agency: 'NGA' + logo: 'nga.png' + cert: 'nga_geoworks_symphony_prod' + return_to_sp_url: 'https://symphony.nga-geoworks.com' + redirect_uris: + - 'https://symphony.nga-geoworks.com/' + - 'https://symphony.nga-geoworks.com/guacamole' + - 'https://symphony.nga-geoworks.com/guacamole/#/' + - 'https://symphony.nga-geoworks.com/secured' restrict_to_deploy_env: 'prod' From 4454aaf960014b52cb80390dd6e4c58a91e66b60 Mon Sep 17 00:00:00 2001 From: Steve Urciuoli Date: Mon, 23 Apr 2018 10:00:03 -0400 Subject: [PATCH 38/39] LG-217 Update DOT Portal cert in prod **Why**: DOT is going live in prod and we have the wrong cert configured **How**: Update the dot_portal_prod.crt file --- certs/sp/dot_portal_prod.crt | 40 ++++++++++++++++++------------------ config/service_providers.yml | 3 ++- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/certs/sp/dot_portal_prod.crt b/certs/sp/dot_portal_prod.crt index d4dd3e0fe24..5cad011abf5 100644 --- a/certs/sp/dot_portal_prod.crt +++ b/certs/sp/dot_portal_prod.crt @@ -1,22 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIDmTCCAoECCQCM4ARJaZKjOjANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMC -VVMxHTAbBgNVBAgMFERpc3RyaWN0IG9mIENvbHVtYmlhMRMwEQYDVQQHDApXYXNo -aW5ndG9uMQwwCgYDVQQKDANET1QxFzAVBgNVBAMMDnBvcnRhbC5kb3QuZ292MSEw -HwYJKoZIhvcNAQkBFhJkb25vdHJlcGx5QGRvdC5nb3YwHhcNMTgwMjI0MjExMTIx -WhcNMTkwNzA5MjExMTIxWjCBkDELMAkGA1UEBhMCVVMxHTAbBgNVBAgMFERpc3Ry -aWN0IG9mIENvbHVtYmlhMRMwEQYDVQQHDApXYXNoaW5ndG9uMRAwDgYDVQQKDAdE -T1QtT1NUMRcwFQYDVQQDDA5wb3J0YWwuZG90LmdvdjEiMCAGCSqGSIb3DQEJARYT -ZG9ub3RyZXBseTJAZG90LmdvdjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAOFbeV3WA3AS6E6r2LBfTvBfrhsn4SDE93gLUCr2JzQgMrn61CGIIAkB96s8 -MjXJiiPFU4MQdIB/+NMojFcv84wwczTtTqZnyJCdgv0DUqqx20fQBzwI1/eBrMDm -kdMbDuV/y3CalLv9RPM7lK6iUmD6kmBsqPus+H2AtKEow0F8F6LTp82URGCDmw6V -srBdxcBs9SVtSOP0EXBgr2rauA6pY9YBc2bjQoDN7qltIRXYIIzAm8BgCYQTTV5F -yk1MII2UBkt0zFP38bh/LuJielQk5ondYndHfU3bq8lk28xfaEMGhb+feVyRTZoA -s4gQrrKY7iqyhbgeD2LIbugWx2sCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAHbp4 -Tz2ugcozzpHDpoKzCNMwLB2FXpL15cZTwl7EULtREtjYpbcLxX4SfpFZjviSV6Lw -4q9jGV0VwiaI7WkyI/YDLyn7t21dm5gkYen+WNwUkxLVce5TCn8tDS9joyIO9OeX -w8p2KvZUEa1EPn4pxvy3MOJFDspcmvTs9KWHzZ+QlkSGZXCnW4KxOa2p1nZOkVPV -t9hrARpBTBqI9Xse8VwmwotwCFMZOVU9Z3Ct5WHBKI8WNyb18fcfw/li+3PZE3wc -hCAnrmI48FEjOGo0/gFoKYparJlKA3nP6cX2Ts4nrYpf4tW2Gpb8yshcMb1DQ7S6 -YeXSB0mY2n9zJU0Edg== +MIIDtjCCAp4CCQC9h+ZVpsjKojANBgkqhkiG9w0BAQsFADCBmTELMAkGA1UEBhMC +VVMxHTAbBgNVBAgMFERpc3RyY2l0IG9mIENvbHVtYmlhMRMwEQYDVQQHDApXYXNo +aW5ndG9uMQwwCgYDVQQKDANET1QxDDAKBgNVBAsMA0RPVDEXMBUGA1UEAwwOcG9y +dGFsLmRvdC5nb3YxITAfBgkqhkiG9w0BCQEWEmRvbm90cmVwbHlAZG90LmdvdjAe +Fw0xODAzMzAxODEzMDVaFw0xOTA4MTIxODEzMDVaMIGfMQswCQYDVQQGEwJVUzEd +MBsGA1UECAwURGlzdHJpY3Qgb2YgQ29sdW1iaWExEzARBgNVBAcMCldhc2hpbmd0 +b24xEjAQBgNVBAoMCURPVCAoT1NUKTEMMAoGA1UECwwDT1NUMRcwFQYDVQQDDA5w +b3J0YWwuZG90LmdvdjEhMB8GCSqGSIb3DQEJARYSZG9ub3RyZXBseUBkb3QuZ292 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyxgfVgYzd3NT2i/DZovH +LWJlUVQYYDqpClCQhUlKDlGD+CjIX+whUYf/dab9u8ZgjH/UeMqWNsqDFDre9kpV +F3snhqWnJPezKBhlgBc/iQJnvC5MK6tnzCpm+6lBMMd9mzgeSMat4LgCv47jQFsW +8E+ZWKw70i/RRbkE+z8c4TBOjMBtpbXT6bptlxeHjGJhza2vM2SrQ+39ZY1r919g +aVuTnKTp/UAzEvG/n5ccVzMUw7Bnels8JUAo7aBt9G0vGa4ZgtEf5+4Hlfg3K0TD +4rwtjnWnOen0eZbZzzHlCwcybkQRjV1t8IUPz4BlmUx5lpFfMXXVn666iaGkcFch ++QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBdVf9IUB3BKpU5CtfLdkUf/LWT3iyO +jLM+BfCrqoliZ2nhqtoBSCFnMQqy501q1pvjoVr8S4RvpSmNUUje+b7qq5NZCRNi +HAWzCD7c7PwI0g12wEmeErFzZnQA8U87uixzzeliRT6f3aBVc+kRd0uFXfotJb4U +2922ZyX4lsmh3VTFluScZZOhrfa7Y12Sju6rqa1DPQuKhcM8qu949+nw+TH8Aqj5 +Es3GW5rmfrrUHY21tJKZCYnYKKsgSUR49Xhg3ogTfMrb2i+yTJQ9CiOaVlaATqcC +VSjrE0ZvbFGfVjClFQe5dMuGrBu6G+RsfYBSs2Z1i3yA1qUnF8ueBfF6 -----END CERTIFICATE----- diff --git a/config/service_providers.yml b/config/service_providers.yml index dea0e9edd10..8e69d9127a9 100644 --- a/config/service_providers.yml +++ b/config/service_providers.yml @@ -579,7 +579,8 @@ production: logo: 'dot.svg' cert: 'dot_portal_prod' redirect_uris: - - 'https://acquia-stage.portal.dot.gov/openid-connect/dot_login' + - 'https://portal.dot.gov/openid-connect/dot_login' + - 'https://fmcsa.portal.dot.gov/openid-connect/dot_login' - 'https://portal.dot.gov/' restrict_to_deploy_env: 'prod' From 66ccfcdd716e7b1a1c7d7e226ac3fefd3541eab1 Mon Sep 17 00:00:00 2001 From: Jonathan Hooper Date: Mon, 23 Apr 2018 11:51:33 -0500 Subject: [PATCH 39/39] Add specs for confirmation and review step (#2106) **Why**: So we can collect the specs for the final steps in the idv process in one place --- spec/features/idv/flow_spec.rb | 28 ----- .../idv/steps/confirmation_step_spec.rb | 29 +++++ spec/features/idv/steps/review_step_spec.rb | 108 ++++++++++++++++++ .../support/idv_examples/confirmation_step.rb | 51 +++++++++ 4 files changed, 188 insertions(+), 28 deletions(-) create mode 100644 spec/features/idv/steps/confirmation_step_spec.rb create mode 100644 spec/features/idv/steps/review_step_spec.rb create mode 100644 spec/support/idv_examples/confirmation_step.rb diff --git a/spec/features/idv/flow_spec.rb b/spec/features/idv/flow_spec.rb index 5a1b49a4201..5838704695f 100644 --- a/spec/features/idv/flow_spec.rb +++ b/spec/features/idv/flow_spec.rb @@ -157,34 +157,6 @@ expect(find('#profile_prev_address1').value).to eq '' end - context 'personal keys information and actions' do - before do - personal_key = 'a1b2c3d4e5f6g7h8' - - @user = sign_in_and_2fa_user - visit verify_session_path - - allow(RandomPhrase).to receive(:to_s).and_return(personal_key) - complete_idv_profile_ok(@user) - end - - scenario 'personal key presented on success' do - expect(page).to have_content(t('headings.personal_key')) - end - - it_behaves_like 'personal key page' - - scenario 'reload personal key page' do - visit current_path - - expect(page).to have_content(t('headings.personal_key')) - - visit current_path - - expect(page).to have_content(t('headings.personal_key')) - end - end - scenario 'attempting to skip OTP phone confirmation redirects to OTP confirmation', :js do different_phone = '555-555-9876' user = sign_in_live_with_2fa diff --git a/spec/features/idv/steps/confirmation_step_spec.rb b/spec/features/idv/steps/confirmation_step_spec.rb new file mode 100644 index 00000000000..25795ede4ab --- /dev/null +++ b/spec/features/idv/steps/confirmation_step_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +feature 'idv confirmation step', :idv_job do + include IdvStepHelper + + it_behaves_like 'idv confirmation step' + it_behaves_like 'idv confirmation step', :oidc + it_behaves_like 'idv confirmation step', :saml + + context 'personal key information and actions' do + before do + personal_key = 'a1b2c3d4e5f6g7h8' + + @user = sign_in_and_2fa_user + visit verify_session_path + + allow(RandomPhrase).to receive(:to_s).and_return(personal_key) + complete_idv_steps_before_confirmation_step(@user) + end + + it 'allows the user to refresh and still displays the personal key' do + # Visit the current path is the same as refreshing + visit current_path + expect(page).to have_content(t('headings.personal_key')) + end + + it_behaves_like 'personal key page' + end +end diff --git a/spec/features/idv/steps/review_step_spec.rb b/spec/features/idv/steps/review_step_spec.rb new file mode 100644 index 00000000000..4746916cebf --- /dev/null +++ b/spec/features/idv/steps/review_step_spec.rb @@ -0,0 +1,108 @@ +require 'rails_helper' + +feature 'idv review step', :idv_job do + include IdvStepHelper + + it 'requires the user to enter the correct password to redirect to confirmation step' do + start_idv_from_sp + complete_idv_steps_before_review_step + + expect(page).to have_content('Jos茅') + expect(page).to have_content('One') + expect(page).to have_content('123 Main St') + expect(page).to have_content('Nowhere, VA 6604') + expect(page).to have_content('January 02, 1980') + expect(page).to have_content('666-66-1234') + expect(page).to have_content('+1 (555) 555-0000') + + fill_in 'Password', with: 'this is not the right password' + click_idv_continue + + expect(page).to have_content(t('idv.errors.incorrect_password')) + expect(page).to have_current_path(verify_review_path) + + fill_in 'Password', with: user_password + click_idv_continue + + expect(page).to have_content(t('headings.personal_key')) + expect(page).to have_current_path(verify_confirmations_path) + end + + context 'choosing to confirm address with phone' do + let(:user) { user_with_2fa } + + before do + start_idv_from_sp + complete_idv_steps_with_phone_before_review_step(user) + end + + it 'does not send a letter and creates a verified profile' do + fill_in 'Password', with: user_password + click_idv_continue + + expect(user.events.account_verified.size).to be(1) + expect(user.profiles.count).to eq 1 + + profile = user.profiles.first + + expect(profile.active?).to eq true + expect(profile.phone_confirmed).to eq true + expect(UspsConfirmation.count).to eq(0) + end + end + + context 'choosing to confirm address with usps' do + let(:user) { user_with_2fa } + let(:sp) { :oidc } + + before do + start_idv_from_sp(sp) + complete_idv_steps_with_usps_before_review_step(user) + end + + it 'sends a letter and creates an unverified profile' do + fill_in 'Password', with: user_password + + expect { click_continue }. + to change { UspsConfirmation.count }.from(0).to(1) + + expect(user.events.account_verified.size).to be(0) + expect(user.profiles.count).to eq 1 + + profile = user.profiles.first + + expect(profile.active?).to eq false + expect(profile.phone_confirmed).to eq false + end + + context 'with an sp' do + it 'sends a letter with a reference the sp' do + fill_in 'Password', with: user_password + click_continue + + usps_confirmation_entry = UspsConfirmation.last.decrypted_entry + + if sp == :saml + expect(usps_confirmation_entry.issuer). + to eq('https://rp1.serviceprovider.com/auth/saml/metadata') + else + expect(usps_confirmation_entry.issuer). + to eq('urn:gov:gsa:openidconnect:sp:server') + end + end + end + + context 'without an sp' do + let(:sp) { nil } + + it 'sends a letter without a reference to the sp' do + fill_in 'Password', with: user_password + click_continue + + usps_confirmation_entry = UspsConfirmation.last.decrypted_entry + + expect(usps_confirmation_entry.issuer).to eq(nil) + end + end + end +end diff --git a/spec/support/idv_examples/confirmation_step.rb b/spec/support/idv_examples/confirmation_step.rb new file mode 100644 index 00000000000..846ad4e3cc0 --- /dev/null +++ b/spec/support/idv_examples/confirmation_step.rb @@ -0,0 +1,51 @@ +shared_examples 'idv confirmation step' do |sp| + context 'after choosing to verify by letter' do + before do + start_idv_from_sp(sp) + complete_idv_steps_with_usps_before_confirmation_step + end + + it 'redirects to the come back later url then to the sp or account' do + click_acknowledge_personal_key + + expect(page).to have_current_path(verify_come_back_later_path) + click_on t('forms.buttons.continue') + + # SAML test SP does not have a return URL, so it does not have a link + # back to the SP + if sp == :oidc + expect(current_url).to start_with('http://localhost:7654/auth/result') + else + expect(page).to have_current_path(account_path) + end + end + end + + context 'after choosing to verify by phone' do + before do + start_idv_from_sp(sp) + complete_idv_steps_with_phone_before_confirmation_step + end + + it 'redirects to the completions page and then to the SP', if: sp.present? do + click_acknowledge_personal_key + + expect(page).to have_current_path(sign_up_completed_path) + + click_on t('forms.buttons.continue') + + if sp == :oidc + expect(current_url).to start_with('http://localhost:7654/auth/result') + else + expect(current_path).to eq(api_saml_auth_path) + end + end + + it 'redirects to the account page', if: sp.nil? do + click_acknowledge_personal_key + + expect(page).to have_content(t('headings.account.verified_account')) + expect(page).to have_current_path(account_path) + end + end +end