Coverage for src/tests/test_strong_verification.py: 99%
377 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-08-28 14:55 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-08-28 14:55 +0000
1import json
2from datetime import date, timedelta
3from unittest.mock import ANY, patch
4from urllib.parse import urlencode
6import grpc
7import pytest
8from google.protobuf import empty_pb2
9from sqlalchemy.sql import or_
11import couchers.jobs.handlers
12import couchers.servicers.account
13from couchers import errors
14from couchers.config import config
15from couchers.crypto import asym_decrypt, b64encode_unpadded
16from couchers.db import session_scope
17from couchers.jobs.handlers import update_badges
18from couchers.jobs.worker import process_job
19from couchers.materialized_views import refresh_materialized_views_rapid
20from couchers.models import (
21 PassportSex,
22 StrongVerificationAttempt,
23 StrongVerificationAttemptStatus,
24 StrongVerificationCallbackEvent,
25 User,
26)
27from couchers.sql import couchers_select as select
28from proto import account_pb2, admin_pb2, api_pb2
29from proto.google.api import httpbody_pb2
30from tests.test_fixtures import ( # noqa
31 account_session,
32 api_session,
33 db,
34 generate_user,
35 push_collector,
36 real_admin_session,
37 real_iris_session,
38 testconfig,
39)
42@pytest.fixture(autouse=True)
43def _(testconfig):
44 pass
47def _emulate_iris_callback(session_id, session_state, reference):
48 assert session_state in ["CREATED", "INITIATED", "FAILED", "ABORTED", "COMPLETED", "REJECTED", "APPROVED"]
49 with real_iris_session() as iris:
50 data = json.dumps(
51 {"session_id": session_id, "session_state": session_state, "session_reference": reference}
52 ).encode("ascii")
53 iris.Webhook(httpbody_pb2.HttpBody(content_type="application/json", data=data))
56default_expiry = date.today() + timedelta(days=5 * 365)
59def do_and_check_sv(
60 user,
61 token,
62 verification_id,
63 sex,
64 dob,
65 document_type,
66 document_number,
67 document_expiry,
68 nationality,
69 return_after=None,
70):
71 iris_token_data = {
72 "merchant_id": 5731012934821982,
73 "session_id": verification_id,
74 "seed": 1674246339,
75 "face_verification": False,
76 "host": "https://passportreader.app",
77 }
78 iris_token = b64encode_unpadded(json.dumps(iris_token_data).encode("utf8"))
80 with account_session(token) as account:
81 # start by initiation
82 with patch("couchers.servicers.account.requests.post") as mock:
83 json_resp1 = {
84 "id": verification_id,
85 "token": iris_token,
86 }
87 mock.return_value = type(
88 "__MockResponse",
89 (),
90 {
91 "status_code": 200,
92 "text": json.dumps(json_resp1),
93 "json": lambda: json_resp1,
94 },
95 )
96 res = account.InitiateStrongVerification(empty_pb2.Empty())
97 mock.assert_called_once_with(
98 "https://passportreader.app/api/v1/session.create",
99 auth=("dummy_pubkey", "dummy_secret"),
100 json={
101 "callback_url": "http://localhost:8888/iris/webhook",
102 "face_verification": False,
103 "reference": ANY,
104 },
105 timeout=10,
106 )
107 reference_data = mock.call_args.kwargs["json"]["reference"]
108 verification_attempt_token = res.verification_attempt_token
109 return_url = f"http://localhost:3000/complete-strong-verification?verification_attempt_token={verification_attempt_token}"
110 assert res.redirect_url == "https://passportreader.app/open?" + urlencode(
111 {"token": iris_token, "redirect_url": return_url}
112 )
114 assert (
115 account.GetStrongVerificationAttemptStatus(
116 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
117 ).status
118 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_USER_TO_OPEN_APP
119 )
121 # ok, now the user downloads the app, scans their id, and Iris ID sends callbacks to the server
122 _emulate_iris_callback(verification_id, "INITIATED", reference_data)
124 with account_session(token) as account:
125 assert (
126 account.GetStrongVerificationAttemptStatus(
127 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
128 ).status
129 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_USER_IN_APP
130 )
132 if return_after == "INITIATED":
133 return reference_data
135 _emulate_iris_callback(verification_id, "COMPLETED", reference_data)
137 with account_session(token) as account:
138 assert (
139 account.GetStrongVerificationAttemptStatus(
140 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
141 ).status
142 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_BACKEND
143 )
145 if return_after == "COMPLETED":
146 return reference_data
148 _emulate_iris_callback(verification_id, "APPROVED", reference_data)
150 with account_session(token) as account:
151 assert (
152 account.GetStrongVerificationAttemptStatus(
153 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
154 ).status
155 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_BACKEND
156 )
158 if return_after == "APPROVED":
159 return reference_data
161 with patch("couchers.jobs.handlers.requests.post") as mock:
162 json_resp2 = {
163 "id": verification_id,
164 "created": "2024-05-11T15:46:46Z",
165 "expires": "2024-05-11T16:17:26Z",
166 "state": "APPROVED",
167 "reference": reference_data,
168 "user_ip": "10.123.123.123",
169 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
170 "given_names": "John Wayne",
171 "surname": "Doe",
172 "nationality": nationality,
173 "sex": sex,
174 "date_of_birth": dob,
175 "document_type": document_type,
176 "document_number": document_number,
177 "expiry_date": document_expiry.isoformat(),
178 "issuing_country": nationality,
179 "issuer": "Department of State, U.S. Government",
180 "portrait": "dGVzdHRlc3R0ZXN0...",
181 }
182 mock.return_value = type(
183 "__MockResponse",
184 (),
185 {
186 "status_code": 200,
187 "text": json.dumps(json_resp2),
188 "json": lambda: json_resp2,
189 },
190 )
191 while process_job():
192 pass
194 mock.assert_called_once_with(
195 "https://passportreader.app/api/v1/session.get",
196 auth=("dummy_pubkey", "dummy_secret"),
197 json={"id": verification_id},
198 timeout=10,
199 )
201 with account_session(token) as account:
202 assert (
203 account.GetStrongVerificationAttemptStatus(
204 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
205 ).status
206 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_SUCCEEDED
207 )
209 with session_scope() as session:
210 verification_attempt = session.execute(
211 select(StrongVerificationAttempt).where(
212 StrongVerificationAttempt.verification_attempt_token == verification_attempt_token
213 )
214 ).scalar_one()
215 assert verification_attempt.user_id == user.id
216 assert verification_attempt.status == StrongVerificationAttemptStatus.succeeded
217 assert verification_attempt.has_full_data
218 assert verification_attempt.passport_encrypted_data
219 # assert verification_attempt.passport_date_of_birth == date(1988, 1, 1)
220 # assert verification_attempt.passport_sex == PassportSex.male
221 assert verification_attempt.has_minimal_data
222 assert verification_attempt.passport_expiry_date == document_expiry
223 assert verification_attempt.passport_nationality == nationality
224 assert verification_attempt.passport_last_three_document_chars == document_number[-3:]
225 assert verification_attempt.iris_token == iris_token
226 assert verification_attempt.iris_session_id == verification_id
228 private_key = bytes.fromhex("e6c2fbf3756b387bc09a458a7b85935718ef3eb1c2777ef41d335c9f6c0ab272")
229 decrypted_data = json.loads(asym_decrypt(private_key, verification_attempt.passport_encrypted_data))
230 assert decrypted_data == json_resp2
232 callbacks = (
233 session.execute(
234 select(StrongVerificationCallbackEvent.iris_status)
235 .where(StrongVerificationCallbackEvent.verification_attempt_id == verification_attempt.id)
236 .order_by(StrongVerificationCallbackEvent.created.asc())
237 )
238 .scalars()
239 .all()
240 )
241 assert callbacks == ["INITIATED", "COMPLETED", "APPROVED"]
244def monkeypatch_sv_config(monkeypatch):
245 new_config = config.copy()
246 new_config["ENABLE_STRONG_VERIFICATION"] = True
247 new_config["IRIS_ID_PUBKEY"] = "dummy_pubkey"
248 new_config["IRIS_ID_SECRET"] = "dummy_secret"
249 new_config["VERIFICATION_DATA_PUBLIC_KEY"] = bytes.fromhex(
250 "dd740a2b2a35bf05041a28257ea439b30f76f056f3698000b71e6470cd82275f"
251 )
253 private_key = bytes.fromhex("e6c2fbf3756b387bc09a458a7b85935718ef3eb1c2777ef41d335c9f6c0ab272")
255 monkeypatch.setattr(couchers.servicers.account, "config", new_config)
256 monkeypatch.setattr(couchers.jobs.handlers, "config", new_config)
259def test_strong_verification_happy_path(db, monkeypatch):
260 monkeypatch_sv_config(monkeypatch)
262 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
263 _, superuser_token = generate_user(is_superuser=True)
265 update_badges(empty_pb2.Empty())
266 refresh_materialized_views_rapid(None)
268 with api_session(token) as api:
269 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
270 assert "strong_verification" not in res.badges
271 assert not res.has_strong_verification
272 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_UNVERIFIED
273 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_UNVERIFIED
274 assert (
275 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
276 == res.has_strong_verification
277 )
279 do_and_check_sv(
280 user,
281 token,
282 verification_id=5731012934821983,
283 sex="MALE",
284 dob="1988-01-01",
285 document_type="PASSPORT",
286 document_number="31195855",
287 document_expiry=default_expiry,
288 nationality="US",
289 )
291 with session_scope() as session:
292 verification_attempt = session.execute(
293 select(StrongVerificationAttempt).where(StrongVerificationAttempt.user_id == user.id)
294 ).scalar_one()
295 assert verification_attempt.status == StrongVerificationAttemptStatus.succeeded
296 assert verification_attempt.passport_date_of_birth == date(1988, 1, 1)
297 assert verification_attempt.passport_sex == PassportSex.male
298 assert verification_attempt.passport_expiry_date == default_expiry
299 assert verification_attempt.passport_nationality == "US"
300 assert verification_attempt.passport_last_three_document_chars == "855"
302 update_badges(empty_pb2.Empty())
303 refresh_materialized_views_rapid(None)
305 # the user should now have strong verification
306 with api_session(token) as api:
307 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
308 assert "strong_verification" in res.badges
309 assert res.has_strong_verification
310 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
311 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
312 assert (
313 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
314 == res.has_strong_verification
315 )
317 # wrong dob = no badge
318 with session_scope() as session:
319 user_ = session.execute(select(User).where(User.id == user.id)).scalar_one()
320 user_.birthdate = date(1988, 1, 2)
322 update_badges(empty_pb2.Empty())
323 refresh_materialized_views_rapid(None)
325 with api_session(token) as api:
326 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
327 assert "strong_verification" not in res.badges
328 assert not res.has_strong_verification
329 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_MISMATCH
330 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
331 assert (
332 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
333 == res.has_strong_verification
334 )
336 # bad gender-sex correspondence = no badge
337 with session_scope() as session:
338 user_ = session.execute(select(User).where(User.id == user.id)).scalar_one()
339 user_.birthdate = date(1988, 1, 1)
340 user_.gender = "Woman"
342 update_badges(empty_pb2.Empty())
343 refresh_materialized_views_rapid(None)
345 with api_session(token) as api:
346 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
347 assert "strong_verification" not in res.badges
348 assert not res.has_strong_verification
349 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
350 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
351 assert (
352 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
353 == res.has_strong_verification
354 )
356 with account_session(token) as account:
357 res = account.GetAccountInfo(empty_pb2.Empty())
358 assert not res.has_strong_verification
359 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
360 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
362 # back to should have a badge
363 with session_scope() as session:
364 user_ = session.execute(select(User).where(User.id == user.id)).scalar_one()
365 user_.gender = "Man"
367 update_badges(empty_pb2.Empty())
368 refresh_materialized_views_rapid(None)
370 with api_session(token) as api:
371 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
372 assert "strong_verification" in res.badges
373 assert res.has_strong_verification
374 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
375 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
376 assert (
377 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
378 == res.has_strong_verification
379 )
380 with account_session(token) as account:
381 assert not any(
382 reminder.HasField("complete_verification_reminder")
383 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
384 )
386 # check has_passport_sex_gender_exception
387 with real_admin_session(superuser_token) as admin:
388 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
389 assert "strong_verification" in res.badges
390 assert res.has_strong_verification
391 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
392 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
394 admin.SetPassportSexGenderException(
395 admin_pb2.SetPassportSexGenderExceptionReq(user=user.username, passport_sex_gender_exception=True)
396 )
397 admin.ChangeUserGender(admin_pb2.ChangeUserGenderReq(user=user.username, gender="Woman"))
399 update_badges(empty_pb2.Empty())
400 refresh_materialized_views_rapid(None)
402 with api_session(token) as api:
403 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
404 assert "strong_verification" in res.badges
405 assert res.has_strong_verification
406 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
407 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
408 assert (
409 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
410 == res.has_strong_verification
411 )
412 with account_session(token) as account:
413 assert not any(
414 reminder.HasField("complete_verification_reminder")
415 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
416 )
418 with real_admin_session(superuser_token) as admin:
419 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
420 assert "strong_verification" in res.badges
421 assert res.has_strong_verification
422 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
423 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
425 # now turn exception off
426 admin.SetPassportSexGenderException(
427 admin_pb2.SetPassportSexGenderExceptionReq(user=user.username, passport_sex_gender_exception=False)
428 )
430 update_badges(empty_pb2.Empty())
431 refresh_materialized_views_rapid(None)
433 with api_session(token) as api:
434 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
435 assert "strong_verification" not in res.badges
436 assert not res.has_strong_verification
437 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
438 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
439 assert (
440 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
441 == res.has_strong_verification
442 )
443 with account_session(token) as account:
444 assert any(
445 reminder.HasField("complete_verification_reminder")
446 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
447 )
449 with real_admin_session(superuser_token) as admin:
450 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
451 assert "strong_verification" not in res.badges
452 assert not res.has_strong_verification
453 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
454 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
457def test_strong_verification_delete_data(db, monkeypatch):
458 monkeypatch_sv_config(monkeypatch)
460 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
461 _, superuser_token = generate_user(is_superuser=True)
463 refresh_materialized_views_rapid(None)
465 with api_session(token) as api:
466 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
467 assert (
468 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
469 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
470 )
472 # can remove SV data even if there is none, should do nothing
473 with account_session(token) as account:
474 account.DeleteStrongVerificationData(empty_pb2.Empty())
476 do_and_check_sv(
477 user,
478 token,
479 verification_id=5731012934821983,
480 sex="MALE",
481 dob="1988-01-01",
482 document_type="PASSPORT",
483 document_number="31195855",
484 document_expiry=default_expiry,
485 nationality="US",
486 )
488 refresh_materialized_views_rapid(None)
490 # the user should now have strong verification
491 with api_session(token) as api:
492 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
493 assert (
494 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
495 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
496 )
498 # check removing SV data
499 with account_session(token) as account:
500 account.DeleteStrongVerificationData(empty_pb2.Empty())
502 refresh_materialized_views_rapid(None)
504 with api_session(token) as api:
505 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
506 assert (
507 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
508 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
509 )
511 with session_scope() as session:
512 assert (
513 len(
514 session.execute(
515 select(StrongVerificationAttempt).where(
516 or_(
517 StrongVerificationAttempt.passport_encrypted_data != None,
518 StrongVerificationAttempt.passport_date_of_birth != None,
519 StrongVerificationAttempt.passport_sex != None,
520 )
521 )
522 )
523 .scalars()
524 .all()
525 )
526 == 0
527 )
530def test_strong_verification_expiry(db, monkeypatch):
531 monkeypatch_sv_config(monkeypatch)
533 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
534 _, superuser_token = generate_user(is_superuser=True)
536 refresh_materialized_views_rapid(None)
538 with api_session(token) as api:
539 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
540 assert (
541 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
542 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
543 )
545 expiry = date.today() + timedelta(days=10)
547 do_and_check_sv(
548 user,
549 token,
550 verification_id=5731012934821983,
551 sex="MALE",
552 dob="1988-01-01",
553 document_type="PASSPORT",
554 document_number="31195855",
555 document_expiry=expiry,
556 nationality="US",
557 )
559 # the user should now have strong verification
560 with api_session(token) as api:
561 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
562 assert res.has_strong_verification
563 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
564 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
566 with session_scope() as session:
567 attempt = session.execute(select(StrongVerificationAttempt)).scalars().one()
568 attempt.passport_expiry_date = date.today() - timedelta(days=2)
570 with api_session(token) as api:
571 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
572 assert not res.has_strong_verification
573 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_UNVERIFIED
574 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_UNVERIFIED
576 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
577 assert not res.has_strong_verification
578 assert not res.has_strong_verification
580 do_and_check_sv(
581 user,
582 token,
583 verification_id=5731012934821985,
584 sex="MALE",
585 dob="1988-01-01",
586 document_type="PASSPORT",
587 document_number="PA41323412",
588 document_expiry=date.today() + timedelta(days=365),
589 nationality="AU",
590 )
592 refresh_materialized_views_rapid(None)
594 with api_session(token) as api:
595 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
596 assert (
597 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
598 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
599 )
602def test_strong_verification_regression(db, monkeypatch):
603 monkeypatch_sv_config(monkeypatch)
605 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
607 do_and_check_sv(
608 user,
609 token,
610 verification_id=5731012934821983,
611 sex="MALE",
612 dob="1988-01-01",
613 document_type="PASSPORT",
614 document_number="31195855",
615 document_expiry=default_expiry,
616 nationality="US",
617 return_after="INITIATED",
618 )
620 with api_session(token) as api:
621 api.Ping(api_pb2.PingReq())
624def test_strong_verification_regression2(db, monkeypatch):
625 monkeypatch_sv_config(monkeypatch)
627 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
629 do_and_check_sv(
630 user,
631 token,
632 verification_id=5731012934821983,
633 sex="MALE",
634 dob="1988-01-01",
635 document_type="PASSPORT",
636 document_number="31195855",
637 document_expiry=default_expiry,
638 nationality="US",
639 return_after="INITIATED",
640 )
642 do_and_check_sv(
643 user,
644 token,
645 verification_id=5731012934821985,
646 sex="MALE",
647 dob="1988-01-01",
648 document_type="PASSPORT",
649 document_number="PA41323412",
650 document_expiry=default_expiry,
651 nationality="AU",
652 )
654 refresh_materialized_views_rapid(None)
656 with api_session(token) as api:
657 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
658 assert (
659 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
660 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
661 )
664def test_strong_verification_disabled(db):
665 user, token = generate_user()
667 with account_session(token) as account:
668 with pytest.raises(grpc.RpcError) as e:
669 account.InitiateStrongVerification(empty_pb2.Empty())
670 assert e.value.code() == grpc.StatusCode.UNAVAILABLE
671 assert e.value.details() == errors.STRONG_VERIFICATION_DISABLED
674def test_strong_verification_delete_data_cant_reverify(db, monkeypatch, push_collector):
675 monkeypatch_sv_config(monkeypatch)
677 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
678 _, superuser_token = generate_user(is_superuser=True)
680 refresh_materialized_views_rapid(None)
682 with api_session(token) as api:
683 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
684 assert (
685 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
686 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
687 )
689 do_and_check_sv(
690 user,
691 token,
692 verification_id=5731012934821983,
693 sex="MALE",
694 dob="1988-01-01",
695 document_type="PASSPORT",
696 document_number="31195855",
697 document_expiry=default_expiry,
698 nationality="US",
699 )
701 refresh_materialized_views_rapid(None)
703 # the user should now have strong verification
704 with api_session(token) as api:
705 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
706 assert (
707 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
708 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
709 )
711 # check removing SV data
712 with account_session(token) as account:
713 account.DeleteStrongVerificationData(empty_pb2.Empty())
715 refresh_materialized_views_rapid(None)
717 with api_session(token) as api:
718 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
719 assert (
720 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
721 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
722 )
724 with session_scope() as session:
725 assert (
726 len(
727 session.execute(
728 select(StrongVerificationAttempt).where(
729 or_(
730 StrongVerificationAttempt.passport_encrypted_data != None,
731 StrongVerificationAttempt.passport_date_of_birth != None,
732 StrongVerificationAttempt.passport_sex != None,
733 )
734 )
735 )
736 .scalars()
737 .all()
738 )
739 == 0
740 )
742 reference_data = do_and_check_sv(
743 user,
744 token,
745 verification_id=5731012934821984,
746 sex="MALE",
747 dob="1988-01-01",
748 document_type="PASSPORT",
749 document_number="31195855",
750 document_expiry=default_expiry,
751 nationality="US",
752 return_after="APPROVED",
753 )
755 with patch("couchers.jobs.handlers.requests.post") as mock:
756 json_resp2 = {
757 "id": 5731012934821984,
758 "created": "2024-05-11T15:46:46Z",
759 "expires": "2024-05-11T16:17:26Z",
760 "state": "APPROVED",
761 "reference": reference_data,
762 "user_ip": "10.123.123.123",
763 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
764 "given_names": "John Wayne",
765 "surname": "Doe",
766 "nationality": "US",
767 "sex": "MALE",
768 "date_of_birth": "1988-01-01",
769 "document_type": "PASSPORT",
770 "document_number": "31195855",
771 "expiry_date": default_expiry.isoformat(),
772 "issuing_country": "US",
773 "issuer": "Department of State, U.S. Government",
774 "portrait": "dGVzdHRlc3R0ZXN0...",
775 }
776 mock.return_value = type(
777 "__MockResponse",
778 (),
779 {
780 "status_code": 200,
781 "text": json.dumps(json_resp2),
782 "json": lambda: json_resp2,
783 },
784 )
785 while process_job():
786 pass
788 mock.assert_called_once_with(
789 "https://passportreader.app/api/v1/session.get",
790 auth=("dummy_pubkey", "dummy_secret"),
791 json={"id": 5731012934821984},
792 timeout=10,
793 )
795 with session_scope() as session:
796 verification_attempt = session.execute(
797 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
798 ).scalar_one()
799 assert verification_attempt.user_id == user.id
800 assert verification_attempt.status == StrongVerificationAttemptStatus.duplicate
802 push_collector.assert_user_push_matches_fields(
803 user.id,
804 ix=1,
805 title="Strong Verification failed",
806 body="You tried to verify with a passport that has already been used for verification. Please use another passport.",
807 )
809 refresh_materialized_views_rapid(None)
811 with api_session(token) as api:
812 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
813 assert (
814 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
815 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
816 )
819def test_strong_verification_duplicate_other_user(db, monkeypatch, push_collector):
820 monkeypatch_sv_config(monkeypatch)
822 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
823 user2, token2 = generate_user(birthdate=date(1988, 1, 1), gender="Man")
824 _, superuser_token = generate_user(is_superuser=True)
826 refresh_materialized_views_rapid(None)
828 with api_session(token) as api:
829 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
830 assert (
831 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
832 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
833 )
835 # can remove SV data even if there is none, should do nothing
836 with account_session(token) as account:
837 account.DeleteStrongVerificationData(empty_pb2.Empty())
839 do_and_check_sv(
840 user,
841 token,
842 verification_id=5731012934821983,
843 sex="MALE",
844 dob="1988-01-01",
845 document_type="PASSPORT",
846 document_number="31195855",
847 document_expiry=default_expiry,
848 nationality="US",
849 )
851 refresh_materialized_views_rapid(None)
853 # the user should now have strong verification
854 with api_session(token) as api:
855 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
856 assert (
857 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
858 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
859 )
861 # check removing SV data
862 with account_session(token) as account:
863 account.DeleteStrongVerificationData(empty_pb2.Empty())
865 refresh_materialized_views_rapid(None)
867 with api_session(token) as api:
868 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
869 assert (
870 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
871 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
872 )
874 with session_scope() as session:
875 assert (
876 len(
877 session.execute(
878 select(StrongVerificationAttempt).where(
879 or_(
880 StrongVerificationAttempt.passport_encrypted_data != None,
881 StrongVerificationAttempt.passport_date_of_birth != None,
882 StrongVerificationAttempt.passport_sex != None,
883 )
884 )
885 )
886 .scalars()
887 .all()
888 )
889 == 0
890 )
892 reference_data = do_and_check_sv(
893 user2,
894 token2,
895 verification_id=5731012934821984,
896 sex="MALE",
897 dob="1988-01-01",
898 document_type="PASSPORT",
899 document_number="31195855",
900 document_expiry=default_expiry,
901 nationality="US",
902 return_after="APPROVED",
903 )
905 with patch("couchers.jobs.handlers.requests.post") as mock:
906 json_resp2 = {
907 "id": 5731012934821984,
908 "created": "2024-05-11T15:46:46Z",
909 "expires": "2024-05-11T16:17:26Z",
910 "state": "APPROVED",
911 "reference": reference_data,
912 "user_ip": "10.123.123.123",
913 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
914 "given_names": "John Wayne",
915 "surname": "Doe",
916 "nationality": "US",
917 "sex": "MALE",
918 "date_of_birth": "1988-01-01",
919 "document_type": "PASSPORT",
920 "document_number": "31195855",
921 "expiry_date": default_expiry.isoformat(),
922 "issuing_country": "US",
923 "issuer": "Department of State, U.S. Government",
924 "portrait": "dGVzdHRlc3R0ZXN0...",
925 }
926 mock.return_value = type(
927 "__MockResponse",
928 (),
929 {
930 "status_code": 200,
931 "text": json.dumps(json_resp2),
932 "json": lambda: json_resp2,
933 },
934 )
935 while process_job():
936 pass
938 mock.assert_called_once_with(
939 "https://passportreader.app/api/v1/session.get",
940 auth=("dummy_pubkey", "dummy_secret"),
941 json={"id": 5731012934821984},
942 timeout=10,
943 )
945 with session_scope() as session:
946 verification_attempt = session.execute(
947 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
948 ).scalar_one()
949 assert verification_attempt.user_id == user2.id
950 assert verification_attempt.status == StrongVerificationAttemptStatus.duplicate
952 push_collector.assert_user_push_matches_fields(
953 user2.id,
954 title="Strong Verification failed",
955 body="You tried to verify with a passport that has already been used for verification. Please use another passport.",
956 )
959def test_strong_verification_non_passport(db, monkeypatch, push_collector):
960 monkeypatch_sv_config(monkeypatch)
962 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
963 _, superuser_token = generate_user(is_superuser=True)
965 reference_data = do_and_check_sv(
966 user,
967 token,
968 verification_id=5731012934821984,
969 sex="MALE",
970 dob="1988-01-01",
971 document_type="IDENTITY_CARD",
972 document_number="31195855",
973 document_expiry=default_expiry,
974 nationality="US",
975 return_after="APPROVED",
976 )
978 with patch("couchers.jobs.handlers.requests.post") as mock:
979 json_resp2 = {
980 "id": 5731012934821984,
981 "created": "2024-05-11T15:46:46Z",
982 "expires": "2024-05-11T16:17:26Z",
983 "state": "APPROVED",
984 "reference": reference_data,
985 "user_ip": "10.123.123.123",
986 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
987 "given_names": "John Wayne",
988 "surname": "Doe",
989 "nationality": "US",
990 "sex": "MALE",
991 "date_of_birth": "1988-01-01",
992 "document_type": "IDENTITY_CARD",
993 "document_number": "31195855",
994 "expiry_date": default_expiry.isoformat(),
995 "issuing_country": "US",
996 "issuer": "Department of State, U.S. Government",
997 "portrait": "dGVzdHRlc3R0ZXN0...",
998 }
999 mock.return_value = type(
1000 "__MockResponse",
1001 (),
1002 {
1003 "status_code": 200,
1004 "text": json.dumps(json_resp2),
1005 "json": lambda: json_resp2,
1006 },
1007 )
1008 while process_job():
1009 pass
1011 mock.assert_called_once_with(
1012 "https://passportreader.app/api/v1/session.get",
1013 auth=("dummy_pubkey", "dummy_secret"),
1014 json={"id": 5731012934821984},
1015 timeout=10,
1016 )
1018 with session_scope() as session:
1019 verification_attempt = session.execute(
1020 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
1021 ).scalar_one()
1022 assert verification_attempt.user_id == user.id
1023 assert verification_attempt.status == StrongVerificationAttemptStatus.failed
1025 push_collector.assert_user_push_matches_fields(
1026 user.id,
1027 title="Strong Verification failed",
1028 body="You tried to verify with a document that is not a passport. You can only use a passport for Strong Verification.",
1029 )