LG-11997 | Conditionally marks users as fraudulent#9928
Conversation
If a user has a fraud_pending_reason, mark them as fraud_review_pending_at when we get a response. changelog: Internal, IPP, Mark users as fraud_review_pending_at if appropriate
| response = nil | ||
|
|
||
| response = proofer.request_proofing_results( | ||
| enrollment.unique_id, enrollment.enrollment_code |
There was a problem hiding this comment.
The assignment on line 102 was useless since we unconditionally assign it here. My editor happened to highlight it while I was working my way through here.
|
|
||
| # MW: What else do we want to log here? | ||
| def handle_fraud_review_pending(enrollment) | ||
| enrollment.profile&.deactivate_for_fraud_review |
There was a problem hiding this comment.
.profile& based on LG-12020. It's rare and weird for profile to be missing, but it can happen.
There was a problem hiding this comment.
sidenote, do we want to update other places where we're accessing properties from profile? (in a future ticket)
There was a problem hiding this comment.
I'm conflicted on this. It's a good idea, but the case in LG-12020 is currently the only one that has ever broken. I'm not sure if I want to create a ticket that will just rot in the backlog.
There was a problem hiding this comment.
Hmm, so in following this code a bit more, it seems like if the profile doesn't exist we wouldn't fail fraud_review_pending?, and then the application would crash at line 382, when we try to call activate_after_passing_in_person on the profile. So it seems to me that we'd want to do the safe navigation operator in lines 382 & 421 (which you already have), and you could probably remove the operator in the handle_fraud_review_pending.
But I feel like if the profile is missing we should bounce outta here sooner, right?
There was a problem hiding this comment.
Oh, nice catch!
I think I added this one when I had LG-12020 still on my mind, and then failed to consider it elsewhere. Maybe I should add a quick test case for this too?
There was a problem hiding this comment.
I deleted my last comment, which turns out to be suggesting a ton of work for little benefit. I updated the instances you referred to, and removed it here. It is indeed impossible to reach this line without a profile.
| reason: 'Successful status update', | ||
| job_name: self.class.name, | ||
| ) | ||
| enrollment.profile.activate_after_passing_in_person |
There was a problem hiding this comment.
This, and 373-380, are now conditional on the user not being fraud_pending?, so this got moved down. We still want the enrollment update below, though.
| # send SMS and email | ||
| send_enrollment_status_sms_notification(enrollment: enrollment) | ||
| # MW: I _think_ this is desired behavior? | ||
| send_failed_email(enrollment.user, enrollment) |
There was a problem hiding this comment.
The idea is that if you get a failure proofing, we'll just send you that, and the fact that we also had you flagged as fraudulent is kind of irrelevant.
| return | ||
| end | ||
|
|
||
| # We want to deactivate them regardless of status: |
There was a problem hiding this comment.
I.e., regardless of response['status']. We'll always take this step if they're so flagged, and then we just needed to tweak the success case to not fully activate them.
|
✅ |
| profile = pending_enrollment.profile | ||
| expect(profile).to be_fraud_review_pending | ||
| expect(profile).not_to be_active | ||
| end |
There was a problem hiding this comment.
The AC may have changed. You and I talked about exiting behavior vs what was in the issue. I would have expected fraud_review_pending to be null and fraud_review_pending_at to have a timestamp on in. I may have missed the ask here and/or the requirements/AC have changed upon diving into the code and discovering existing processes. If they have changed- we can update the AC in the ticket.
There was a problem hiding this comment.
Thanks for flagging this. It did change; there is now a link in the Jira ticket to the Slack thread where it came up. It turns out that the existing pattern is to leave fraud_pending_reason in place.
I tried to be clever with expect(profile).to be_fraud_review_pending?, which exercises this method:
def fraud_review_pending?
fraud_review_pending_at.present?
endBut in hindsight I think it might be clearer if I just did expect(profile.fraud_review_pending_at).to eq (Time.zone.now) (or similar), rather than relying on a magic matcher + non-obvious helper method in Profile.
app/services/analytics_events.rb
Outdated
| **extra | ||
| ) | ||
| track_event( | ||
| :idv_in_person_usps_proofing_results_job_user_sent_to_fraud_review, |
There was a problem hiding this comment.
TIL that we are moving to this model for new events, vs. a string, and it must match the method name. This was discussed as a previous eng huddle, but apparently one where I wasn't present.
| profile.deactivate_for_gpo_verification if gpo_verification_needed | ||
| if fraud_pending_reason.present? && !gpo_verification_needed | ||
|
|
||
| if fraud_pending_reason.present? && !gpo_verification_needed && !in_person_verification_needed |
There was a problem hiding this comment.
@svalexander This is the fix you helped catch yesterday! 🥳 I also updated the ProfileMakerSpec to cover this. I think it would have taken way longer for me to find this if you hadn't caught it; thanks!
| it 'is not fraud_review_pending?' do | ||
| expect(profile.fraud_review_pending?).to eq(false) | ||
| end | ||
| end |
There was a problem hiding this comment.
These test my change to the ProfileMaker. Without the change, but with this test, the GPO verification case passes, but the IPP one did not.
|
|
||
| # MW: What else do we want to log here? | ||
| def handle_fraud_review_pending(enrollment) | ||
| enrollment.profile&.deactivate_for_fraud_review |
There was a problem hiding this comment.
I'm conflicted on this. It's a good idea, but the case in LG-12020 is currently the only one that has ever broken. I'm not sure if I want to create a ticket that will just rot in the backlog.
| in_person_email_reminder_final_benchmark_in_days: 1 | ||
| in_person_email_reminder_late_benchmark_in_days: 4 | ||
| in_person_proofing_enabled: false | ||
| in_person_proofing_enforce_tmx: false |
There was a problem hiding this comment.
Global default is false
| hmac_fingerprinter_key_queue: '["11111111111111111111111111111111", "22222222222222222222222222222222"]' | ||
| identity_pki_local_dev: true | ||
| in_person_proofing_enabled: true | ||
| in_person_proofing_enforce_tmx: true |
| stub_request_passed_proofing_results | ||
| end | ||
|
|
||
| it 'updates the enrollment' do |
There was a problem hiding this comment.
This one example happens whether the flag is true or false, because it happens before we look at fraud statuses. It feels a little lonely out here though.
|
I added a commit which makes FakeAnalytics add With that, this test passes: it 'logs a user_sent_to_fraud_review analytics event' do
job.perform(Time.zone.now)
expect(job_analytics).to have_logged_event(
:idv_in_person_usps_proofing_results_job_user_sent_to_fraud_review,
hash_including(
enrollment_code: pending_enrollment.enrollment_code,
user_id: user.uuid,
),
)
endHowever, the change would require updating a ton of existing tests which make assertions about what is logged and aren't expecting |
This reverts commit 952b935. (This is good stuff IMHO, but it belongs in its own PR.)
JackRyan1989
left a comment
There was a problem hiding this comment.
Looks real good! Just sending over some comments but willing to approve upon further discussion.
|
|
||
| # MW: What else do we want to log here? | ||
| def handle_fraud_review_pending(enrollment) | ||
| enrollment.profile&.deactivate_for_fraud_review |
There was a problem hiding this comment.
Hmm, so in following this code a bit more, it seems like if the profile doesn't exist we wouldn't fail fraud_review_pending?, and then the application would crash at line 382, when we try to call activate_after_passing_in_person on the profile. So it seems to me that we'd want to do the safe navigation operator in lines 382 & 421 (which you already have), and you could probably remove the operator in the handle_fraud_review_pending.
But I feel like if the profile is missing we should bounce outta here sooner, right?
| end | ||
|
|
||
| def handle_fraud_review_pending(enrollment) | ||
| return unless IdentityConfig.store.in_person_proofing_enforce_tmx |
There was a problem hiding this comment.
So this is totally a super nit, but I'm wondering if it makes sense to move this config check into it's own method like we do for ipp_enabled on line 65 and then use it on line 432 to determine if we should call handle_fraud_review_pending? I kinda like that pattern of escalating and making more explicit the prerequisites for a method to be called. But also, this is non-blocking I think.
There was a problem hiding this comment.
That's exactly the pattern @svalexander took in her PR for LG-12091. I like it!
(Some part of my brain really wants to have the method itself enforce this, but I don't think it's necessary.)
| email_type: 'Success', | ||
| job_name: self.class.name, | ||
| ) | ||
| unless fraud_result_pending?(enrollment) |
There was a problem hiding this comment.
Do we need to check the config setting here too? Based on your specs I think no, but I'm not totally clear on when fraud_pending_reason is available on the enrollment profile.
There was a problem hiding this comment.
Oh! I think we should.
We're not the first ones to use fraud_pending_reason, so the feature flag should toggle whether or not we act on it here. Seems like I need to clean up the test as well.
There was a problem hiding this comment.
Better yet, your suggestion in https://github.com/18F/identity-idp/pull/9928/files#r1467975629 gets us this for free.
n1zyy
left a comment
There was a problem hiding this comment.
@JackRyan1989 Thanks for your thorough review here—you caught a few different things I missed! (And thanks also for pairing earlier on the logging issue I had.)
|
|
||
| # MW: What else do we want to log here? | ||
| def handle_fraud_review_pending(enrollment) | ||
| enrollment.profile&.deactivate_for_fraud_review |
There was a problem hiding this comment.
Oh, nice catch!
I think I added this one when I had LG-12020 still on my mind, and then failed to consider it elsewhere. Maybe I should add a quick test case for this too?
| end | ||
|
|
||
| def handle_fraud_review_pending(enrollment) | ||
| return unless IdentityConfig.store.in_person_proofing_enforce_tmx |
There was a problem hiding this comment.
That's exactly the pattern @svalexander took in her PR for LG-12091. I like it!
(Some part of my brain really wants to have the method itself enforce this, but I don't think it's necessary.)
| email_type: 'Success', | ||
| job_name: self.class.name, | ||
| ) | ||
| unless fraud_result_pending?(enrollment) |
There was a problem hiding this comment.
Oh! I think we should.
We're not the first ones to use fraud_pending_reason, so the feature flag should toggle whether or not we act on it here. Seems like I need to clean up the test as well.
…r fraud-pending in-person users When the identity verification report was built the fraud-review functionality did not apply to in-person pending. This was changed in #9928, but the report was not updated to filter fraud-pending users out of the successfully verified count for in-person events. This commit modifies the cloudwatch query to filter those users who are not verified from being counted as successfully verified. [skip changelog]
… to filter fraud-pending in-person users (#10798) When the identity verification report was built the fraud-review functionality did not apply to in-person pending. This was changed in #9928, but the report was not updated to filter fraud-pending users out of the successfully verified count for in-person events. This commit modifies the cloudwatch query to filter those users who are not verified from being counted as successfully verified. [skip changelog]
… to filter fraud-pending in-person users (18F#10798) When the identity verification report was built the fraud-review functionality did not apply to in-person pending. This was changed in 18F#9928, but the report was not updated to filter fraud-pending users out of the successfully verified count for in-person events. This commit modifies the cloudwatch query to filter those users who are not verified from being counted as successfully verified. [skip changelog]
🎫 Ticket
Link to the relevant ticket:
LG-11997
🛠 Summary of changes
If a user has a fraud_pending_reason from TMX, mark them as fraud_review_pending_at when we get a response back from the USPS.
📜 Testing Plan
It may make sense to slow-roll this until Keith's PR for LG-12016 is ready, because his story will come first for a user.