diff --git a/app/services/duplicate_profile_checker.rb b/app/services/duplicate_profile_checker.rb index d8136d4ea22..06c6e028f7b 100644 --- a/app/services/duplicate_profile_checker.rb +++ b/app/services/duplicate_profile_checker.rb @@ -16,8 +16,10 @@ def check_for_duplicate_profiles pii = cacher.fetch(profile.id) duplicate_ssn_finder = Idv::DuplicateSsnFinder.new(user:, ssn: pii[:ssn]) - associated_profiles = duplicate_ssn_finder.associated_facial_match_profiles_with_ssn - if !duplicate_ssn_finder.ial2_profile_ssn_is_unique? + associated_profiles = duplicate_ssn_finder.duplicate_facial_match_profiles( + service_provider: sp.issuer, + ) + if associated_profiles ids = associated_profiles.map(&:id) user_session[:duplicate_profile_ids] = ids end diff --git a/app/services/idv/duplicate_ssn_finder.rb b/app/services/idv/duplicate_ssn_finder.rb index 09577302584..1b75f323f43 100644 --- a/app/services/idv/duplicate_ssn_finder.rb +++ b/app/services/idv/duplicate_ssn_finder.rb @@ -13,14 +13,16 @@ def ssn_is_unique? Profile.where(ssn_signature: ssn_signatures).where.not(user_id: user.id).empty? end - def associated_facial_match_profiles_with_ssn - Profile.active.facial_match.where(ssn_signature: ssn_signatures) - .where(initiating_service_provider_issuer: sp_eligible_for_one_account) + def duplicate_facial_match_profiles(service_provider:) + Profile + .active + .facial_match + .where(ssn_signature: ssn_signatures) + .joins('INNER JOIN identities ON identities.user_id = profiles.user_id') + .where(identities: { service_provider: service_provider }) + .where(identities: { deleted_at: nil }) .where.not(user_id: user.id) - end - - def ial2_profile_ssn_is_unique? - associated_facial_match_profiles_with_ssn.empty? + .distinct end # Due to potentially inconsistent normalization of stored SSNs in the past, we must check: diff --git a/spec/services/duplicate_profile_checker_spec.rb b/spec/services/duplicate_profile_checker_spec.rb index b037f2a08a2..2ba069741df 100644 --- a/spec/services/duplicate_profile_checker_spec.rb +++ b/spec/services/duplicate_profile_checker_spec.rb @@ -48,6 +48,13 @@ context 'when user has accounts with matching profile' do let(:user2) { create(:user, :fully_registered) } + let!(:user2_identity) do + create( + :service_provider_identity, + user: user2, + service_provider: sp.issuer, + ) + end let!(:profile2) do profile = create( :profile, diff --git a/spec/services/idv/duplicate_ssn_finder_spec.rb b/spec/services/idv/duplicate_ssn_finder_spec.rb index 5f8000068ae..26afcbeacd9 100644 --- a/spec/services/idv/duplicate_ssn_finder_spec.rb +++ b/spec/services/idv/duplicate_ssn_finder_spec.rb @@ -7,11 +7,6 @@ subject { described_class.new(ssn: ssn, user: user) } - before do - allow(IdentityConfig.store).to receive(:eligible_one_account_providers) - .and_return([OidcAuthHelper::OIDC_FACIAL_MATCH_ISSUER]) - end - context 'when the ssn is unique' do it { expect(subject.ssn_is_unique?).to eq(true) } end @@ -68,79 +63,86 @@ end end - describe '#associated_facial_match_profiles_with_ssn' do + describe '#duplicate_facial_match_profiles' do + let(:service_provider) { OidcAuthHelper::OIDC_FACIAL_MATCH_ISSUER } + let(:other_service_provider) { OidcAuthHelper::OIDC_FACIAL_MATCH_ISSUER } let(:ssn) { '123-45-6789' } let(:user) { create(:user) } + let(:other_user) { create(:user) } + let(:profile) do + create( + :profile, + idv_level: :unsupervised_with_selfie, + pii: { ssn: ssn }, + user: user, + active: true, + ) + end + let!(:identity) do + create( + :service_provider_identity, + service_provider: service_provider, + user: user, + ) + end + let(:other_profile_idv_level) { :unsupervised_with_selfie } + let(:other_profile_ssn) { ssn } + let(:other_profile_active) { true } + let!(:other_profile) do + create( + :profile, + idv_level: other_profile_idv_level, + pii: { ssn: other_profile_ssn }, + user: other_user, + active: other_profile_active, + ) + end + let!(:other_identity) do + create( + :service_provider_identity, + service_provider: other_service_provider, + user: other_user, + ) + end subject { described_class.new(ssn: ssn, user: user) } - before do - allow(IdentityConfig.store).to receive(:eligible_one_account_providers) - .and_return([OidcAuthHelper::OIDC_FACIAL_MATCH_ISSUER]) + context 'when the other profile is active, has matching SSN and is at facial match IDV level' do + it 'returns list with matching profile' do + expect(subject.duplicate_facial_match_profiles(service_provider:).last.id) + .to eq(other_profile.id) + end end - context 'when profile is IAL2' do - context 'when ssn is taken by different profile by and is IAL2' do - it 'returns list different profile' do - create(:profile, :facial_match_proof, pii: { ssn: ssn }, user: user, active: true) + context 'when the other profile is not at facial match IDV level' do + let(:other_profile_idv_level) { :legacy_unsupervised } - create(:profile, :facial_match_proof, pii: { ssn: ssn }, active: true) - expect(subject.associated_facial_match_profiles_with_ssn.size).to eq(1) - end + it 'is empty' do + expect(subject.duplicate_facial_match_profiles(service_provider:)).to be_empty end + end - context 'when ssn is taken by different profile by and is not IAL2' do - it 'returns empty array' do - create(:profile, :facial_match_proof, pii: { ssn: ssn }, user: user, active: true) - - create(:profile, pii: { ssn: ssn }, active: true) - expect(subject.associated_facial_match_profiles_with_ssn.size).to eq(0) - end - end + context 'when the other profile has a different SSN' do + let(:other_profile_ssn) { '555-66-7788' } - context 'when ssn is not taken by other profiles' do - it 'returns empty array' do - create(:profile, :facial_match_proof, pii: { ssn: ssn }, user: user, active: true) - expect(subject.associated_facial_match_profiles_with_ssn.size).to eq(0) - end + it 'is empty' do + expect(subject.duplicate_facial_match_profiles(service_provider:)).to be_empty end end - end - describe '#ial2_profile_ssn_is_unique?' do - let(:ssn) { '123-45-6789' } - let(:user) { create(:user) } - - subject { described_class.new(ssn: ssn, user: user) } + context 'when the other profile is not active' do + let(:other_profile_active) { false } - before do - allow(IdentityConfig.store).to receive(:eligible_one_account_providers) - .and_return([OidcAuthHelper::OIDC_FACIAL_MATCH_ISSUER]) - end - context 'when profile is IAL2' do - context 'when ssn is taken by different profile by and is IAL2' do - it 'returns false' do - create(:profile, :facial_match_proof, pii: { ssn: ssn }, user: user, active: true) - - create(:profile, :facial_match_proof, pii: { ssn: ssn }, active: true) - expect(subject.ial2_profile_ssn_is_unique?).to eq false - end + it 'is empty' do + expect(subject.duplicate_facial_match_profiles(service_provider:)).to be_empty end + end - context 'when ssn is taken by different profile by and is not IAL2' do - it 'returns true' do - create(:profile, :facial_match_proof, pii: { ssn: ssn }, user: user, active: true) - - create(:profile, pii: { ssn: ssn }, active: true) - expect(subject.ial2_profile_ssn_is_unique?).to eq true - end - end + context 'when the other profile has not been active with the service_provider' do + let(:other_service_provider) { OidcAuthHelper::OIDC_ISSUER } - context 'when ssn is not taken by other profiles' do - it 'returns true' do - create(:profile, :facial_match_proof, pii: { ssn: ssn }, user: user, active: true) - expect(subject.ial2_profile_ssn_is_unique?).to eq true - end + it 'is empty' do + expect(subject.duplicate_facial_match_profiles(service_provider:)).to be_empty end end end