Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/concerns/ial2_profile_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions app/services/analytics_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
31 changes: 22 additions & 9 deletions app/services/pii/cacher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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
31 changes: 29 additions & 2 deletions spec/services/pii/cacher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
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(
first_name: 'Test',
last_name: 'Testerson',
dob: '2023-01-01',
zipcode: '10000',
ssn: '123-45-6789',
ssn: ssn,
)
end
let(:active_profile) do
Expand All @@ -26,7 +27,7 @@
last_name: 'Testerson2',
dob: '2023-01-01',
zipcode: '10000',
ssn: '999-99-9999',
ssn: '999999999',
)
end
let(:pending_profile) do
Expand Down Expand Up @@ -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
Expand Down