diff --git a/app/controllers/concerns/ial2_profile_concern.rb b/app/controllers/concerns/ial2_profile_concern.rb index 679660a8d59..4fb98d2640f 100644 --- a/app/controllers/concerns/ial2_profile_concern.rb +++ b/app/controllers/concerns/ial2_profile_concern.rb @@ -18,7 +18,7 @@ def cache_profiles(raw_password) private def cache_profile_and_handle_errors(raw_password, profile) - cacher = Pii::Cacher.new(current_user, user_session) + cacher = Pii::Cacher.new(current_user, user_session, analytics:) begin cacher.save(raw_password, profile) rescue Encryption::EncryptionError => err diff --git a/app/services/analytics_events.rb b/app/services/analytics_events.rb index f636030b53d..e356a8ca405 100644 --- a/app/services/analytics_events.rb +++ b/app/services/analytics_events.rb @@ -738,6 +738,10 @@ def external_redirect(redirect_url:, step: nil, location: nil, flow: nil, **extr ) end + def fingerprints_rotated + track_event(:fingerprints_rotated) + end + # The user chose to "forget all browsers" def forget_all_browsers_submitted track_event('Forget All Browsers Submitted') diff --git a/app/services/pii/cacher.rb b/app/services/pii/cacher.rb index cf47175d2b4..1e644f2cdb2 100644 --- a/app/services/pii/cacher.rb +++ b/app/services/pii/cacher.rb @@ -2,17 +2,18 @@ module Pii class Cacher - attr_reader :user, :user_session + attr_reader :user, :user_session, :analytics - def initialize(user, user_session) + def initialize(user, user_session, analytics: nil) @user = user @user_session = user_session + @analytics = analytics end def save(user_password, profile = user.active_profile) decrypted_pii = profile.decrypt_pii(user_password) if profile save_decrypted_pii(decrypted_pii, profile.id) if decrypted_pii - rotate_fingerprints(profile, decrypted_pii) if stale_fingerprints?(profile, decrypted_pii) + rotate_fingerprints_if_stale(profile, decrypted_pii) decrypted_pii end @@ -43,12 +44,18 @@ def delete private - def rotate_fingerprints(profile, pii) - KeyRotator::HmacFingerprinter.new.rotate( - user: user, - profile: profile, - pii_attributes: pii, - ) + def rotate_fingerprints_if_stale(profile, pii) + return unless profile.present? && pii.present? + pii_copy = pii_with_normalized_ssn(pii) + + if stale_fingerprints?(profile, pii_copy) + analytics&.fingerprints_rotated + KeyRotator::HmacFingerprinter.new.rotate( + user: user, + profile: profile, + pii_attributes: pii_copy, + ) + end end def stale_fingerprints?(profile, pii) @@ -67,5 +74,11 @@ def stale_compound_pii_signature?(profile, pii) return false unless compound_pii Pii::Fingerprinter.stale?(compound_pii, profile.name_zip_birth_year_signature) end + + def pii_with_normalized_ssn(pii) + pii_copy = pii.dup + pii_copy.ssn = SsnFormatter.normalize(pii_copy.ssn) + pii_copy + end end end diff --git a/spec/services/pii/cacher_spec.rb b/spec/services/pii/cacher_spec.rb index 38fee399747..e4a3777e97a 100644 --- a/spec/services/pii/cacher_spec.rb +++ b/spec/services/pii/cacher_spec.rb @@ -4,6 +4,7 @@ let(:password) { 'salty peanuts are best' } let(:user) { create(:user, :with_phone, password: password) } let(:user_session) { {}.with_indifferent_access } + let(:ssn) { '123456789' } let(:active_pii) do Pii::Attributes.new( @@ -11,7 +12,7 @@ last_name: 'Testerson', dob: '2023-01-01', zipcode: '10000', - ssn: '123-45-6789', + ssn: ssn, ) end let(:active_profile) do @@ -26,7 +27,7 @@ last_name: 'Testerson2', dob: '2023-01-01', zipcode: '10000', - ssn: '999-99-9999', + ssn: '999999999', ) end let(:pending_profile) do @@ -56,6 +57,32 @@ expect(decrypted_pending_session_pii).to eq(pending_pii.to_json) end + context 'when the original signature was based on an unnormalized SSN' do + before do + stub_analytics + end + + let(:ssn) { '123-45-6789' } + + it 'updates the ssn_signature based on the normalized form' do + old_ssn_signature = active_profile.ssn_signature + + # Create a new user object to drop the memoized encrypted attributes + reloaded_user = User.find(user.id) + + described_class.new(reloaded_user, user_session, analytics: @analytics) + .save(password, active_profile) + + active_profile.reload + + expect(active_profile.ssn_signature).to_not eq(old_ssn_signature) + expect(active_profile.ssn_signature).to eq(Pii::Fingerprinter.fingerprint('123456789')) + expect(@analytics).to have_logged_event( + :fingerprints_rotated, + ) + end + end + it 'updates PII bundle fingerprints when keys are rotated' do old_ssn_signature = active_profile.ssn_signature old_compound_pii_fingerprint = active_profile.name_zip_birth_year_signature