Coverage for src / tests / test_strong_verification.py: 99%
380 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-14 09:03 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-14 09:03 +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 import select, update
10from sqlalchemy.sql import or_
12import couchers.jobs.handlers
13import couchers.servicers.account
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.proto import account_pb2, admin_pb2, api_pb2
28from couchers.proto.google.api import httpbody_pb2
29from tests.fixtures.db import generate_user
30from tests.fixtures.misc import PushCollector
31from tests.fixtures.sessions import account_session, api_session, real_admin_session, real_iris_session
34@pytest.fixture(autouse=True)
35def _(testconfig):
36 pass
39def _emulate_iris_callback(session_id, session_state, reference):
40 assert session_state in ["CREATED", "INITIATED", "FAILED", "ABORTED", "COMPLETED", "REJECTED", "APPROVED"]
41 with real_iris_session() as iris:
42 data = json.dumps(
43 {"session_id": session_id, "session_state": session_state, "session_reference": reference}
44 ).encode("ascii")
45 iris.Webhook(httpbody_pb2.HttpBody(content_type="application/json", data=data))
48default_expiry = date.today() + timedelta(days=5 * 365)
51def do_and_check_sv(
52 user,
53 token,
54 verification_id,
55 sex,
56 dob,
57 document_type,
58 document_number,
59 document_expiry,
60 nationality,
61 return_after=None,
62):
63 iris_token_data = {
64 "merchant_id": 5731012934821982,
65 "session_id": verification_id,
66 "seed": 1674246339,
67 "face_verification": False,
68 "host": "https://passportreader.app",
69 }
70 iris_token = b64encode_unpadded(json.dumps(iris_token_data).encode("utf8"))
72 with account_session(token) as account:
73 # start by initiation
74 with patch("couchers.servicers.account.requests.post") as mock:
75 json_resp1 = {
76 "id": verification_id,
77 "token": iris_token,
78 }
79 mock.return_value = type(
80 "__MockResponse",
81 (),
82 {
83 "status_code": 200,
84 "text": json.dumps(json_resp1),
85 "json": lambda: json_resp1,
86 },
87 )
88 res = account.InitiateStrongVerification(empty_pb2.Empty())
89 mock.assert_called_once_with(
90 "https://passportreader.app/api/v1/session.create",
91 auth=("dummy_pubkey", "dummy_secret"),
92 json={
93 "callback_url": "http://localhost:8888/iris/webhook",
94 "face_verification": False,
95 "passport_only": True,
96 "reference": ANY,
97 },
98 timeout=10,
99 verify="/etc/ssl/certs/ca-certificates.crt",
100 )
101 reference_data = mock.call_args.kwargs["json"]["reference"]
102 verification_attempt_token = res.verification_attempt_token
103 return_url = f"http://localhost:3000/complete-strong-verification?verification_attempt_token={verification_attempt_token}"
104 assert res.redirect_url == "https://passportreader.app/open?" + urlencode(
105 {"token": iris_token, "redirect_url": return_url}
106 )
108 assert (
109 account.GetStrongVerificationAttemptStatus(
110 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
111 ).status
112 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_USER_TO_OPEN_APP
113 )
115 # ok, now the user downloads the app, scans their id, and Iris ID sends callbacks to the server
116 _emulate_iris_callback(verification_id, "INITIATED", reference_data)
118 with account_session(token) as account:
119 assert (
120 account.GetStrongVerificationAttemptStatus(
121 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
122 ).status
123 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_USER_IN_APP
124 )
126 if return_after == "INITIATED":
127 return reference_data
129 _emulate_iris_callback(verification_id, "COMPLETED", reference_data)
131 with account_session(token) as account:
132 assert (
133 account.GetStrongVerificationAttemptStatus(
134 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
135 ).status
136 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_BACKEND
137 )
139 if return_after == "COMPLETED": 139 ↛ 140line 139 didn't jump to line 140 because the condition on line 139 was never true
140 return reference_data
142 _emulate_iris_callback(verification_id, "APPROVED", reference_data)
144 with account_session(token) as account:
145 assert (
146 account.GetStrongVerificationAttemptStatus(
147 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
148 ).status
149 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_BACKEND
150 )
152 if return_after == "APPROVED":
153 return reference_data
155 with patch("couchers.jobs.handlers.requests.post") as mock:
156 json_resp2 = {
157 "id": verification_id,
158 "created": "2024-05-11T15:46:46Z",
159 "expires": "2024-05-11T16:17:26Z",
160 "state": "APPROVED",
161 "reference": reference_data,
162 "user_ip": "10.123.123.123",
163 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
164 "given_names": "John Wayne",
165 "surname": "Doe",
166 "nationality": nationality,
167 "sex": sex,
168 "date_of_birth": dob,
169 "document_type": document_type,
170 "document_number": document_number,
171 "expiry_date": document_expiry.isoformat(),
172 "issuing_country": nationality,
173 "issuer": "Department of State, U.S. Government",
174 "portrait": "dGVzdHRlc3R0ZXN0...",
175 }
176 mock.return_value = type(
177 "__MockResponse",
178 (),
179 {
180 "status_code": 200,
181 "text": json.dumps(json_resp2),
182 "json": lambda: json_resp2,
183 },
184 )
185 while process_job():
186 pass
188 mock.assert_called_once_with(
189 "https://passportreader.app/api/v1/session.get",
190 auth=("dummy_pubkey", "dummy_secret"),
191 json={"id": verification_id},
192 timeout=10,
193 verify="/etc/ssl/certs/ca-certificates.crt",
194 )
196 with account_session(token) as account:
197 assert (
198 account.GetStrongVerificationAttemptStatus(
199 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
200 ).status
201 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_SUCCEEDED
202 )
204 with session_scope() as session:
205 verification_attempt = session.execute(
206 select(StrongVerificationAttempt).where(
207 StrongVerificationAttempt.verification_attempt_token == verification_attempt_token
208 )
209 ).scalar_one()
210 assert verification_attempt.user_id == user.id
211 assert verification_attempt.status == StrongVerificationAttemptStatus.succeeded
212 assert verification_attempt.has_full_data
213 assert verification_attempt.passport_encrypted_data
214 # assert verification_attempt.passport_date_of_birth == date(1988, 1, 1)
215 # assert verification_attempt.passport_sex == PassportSex.male
216 assert verification_attempt.has_minimal_data
217 assert verification_attempt.passport_expiry_date == document_expiry
218 assert verification_attempt.passport_nationality == nationality
219 assert verification_attempt.passport_last_three_document_chars == document_number[-3:]
220 assert verification_attempt.iris_token == iris_token
221 assert verification_attempt.iris_session_id == verification_id
223 private_key = bytes.fromhex("e6c2fbf3756b387bc09a458a7b85935718ef3eb1c2777ef41d335c9f6c0ab272")
224 decrypted_data = json.loads(asym_decrypt(private_key, verification_attempt.passport_encrypted_data))
225 assert decrypted_data == json_resp2
227 callbacks = (
228 session.execute(
229 select(StrongVerificationCallbackEvent.iris_status)
230 .where(StrongVerificationCallbackEvent.verification_attempt_id == verification_attempt.id)
231 .order_by(StrongVerificationCallbackEvent.created.asc())
232 )
233 .scalars()
234 .all()
235 )
236 assert callbacks == ["INITIATED", "COMPLETED", "APPROVED"]
239def monkeypatch_sv_config(monkeypatch):
240 new_config = config.copy()
241 new_config["ENABLE_STRONG_VERIFICATION"] = True
242 new_config["IRIS_ID_PUBKEY"] = "dummy_pubkey"
243 new_config["IRIS_ID_SECRET"] = "dummy_secret"
244 new_config["VERIFICATION_DATA_PUBLIC_KEY"] = bytes.fromhex(
245 "dd740a2b2a35bf05041a28257ea439b30f76f056f3698000b71e6470cd82275f"
246 )
248 private_key = bytes.fromhex("e6c2fbf3756b387bc09a458a7b85935718ef3eb1c2777ef41d335c9f6c0ab272")
250 monkeypatch.setattr(couchers.servicers.account, "config", new_config)
251 monkeypatch.setattr(couchers.jobs.handlers, "config", new_config)
254def test_strong_verification_happy_path(db, monkeypatch):
255 monkeypatch_sv_config(monkeypatch)
257 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
258 _, superuser_token = generate_user(is_superuser=True)
260 update_badges(empty_pb2.Empty())
261 refresh_materialized_views_rapid(empty_pb2.Empty())
263 with api_session(token) as api:
264 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
265 assert "strong_verification" not in res.badges
266 assert not res.has_strong_verification
267 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_UNVERIFIED
268 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_UNVERIFIED
269 assert (
270 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
271 == res.has_strong_verification
272 )
274 do_and_check_sv(
275 user,
276 token,
277 verification_id=5731012934821983,
278 sex="MALE",
279 dob="1988-01-01",
280 document_type="PASSPORT",
281 document_number="31195855",
282 document_expiry=default_expiry,
283 nationality="US",
284 )
286 with session_scope() as session:
287 verification_attempt = session.execute(
288 select(StrongVerificationAttempt).where(StrongVerificationAttempt.user_id == user.id)
289 ).scalar_one()
290 assert verification_attempt.status == StrongVerificationAttemptStatus.succeeded
291 assert verification_attempt.passport_date_of_birth == date(1988, 1, 1)
292 assert verification_attempt.passport_sex == PassportSex.male
293 assert verification_attempt.passport_expiry_date == default_expiry
294 assert verification_attempt.passport_nationality == "US"
295 assert verification_attempt.passport_last_three_document_chars == "855"
297 update_badges(empty_pb2.Empty())
298 refresh_materialized_views_rapid(empty_pb2.Empty())
300 # the user should now have strong verification
301 with api_session(token) as api:
302 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
303 assert "strong_verification" in res.badges
304 assert res.has_strong_verification
305 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
306 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
307 assert (
308 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
309 == res.has_strong_verification
310 )
312 # wrong dob = no badge
313 with session_scope() as session:
314 session.execute(update(User).where(User.id == user.id).values(birthdate=date(1988, 1, 2)))
316 update_badges(empty_pb2.Empty())
317 refresh_materialized_views_rapid(empty_pb2.Empty())
319 with api_session(token) as api:
320 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
321 assert "strong_verification" not in res.badges
322 assert not res.has_strong_verification
323 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_MISMATCH
324 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
325 assert (
326 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
327 == res.has_strong_verification
328 )
330 # bad gender-sex correspondence = no badge
331 with session_scope() as session:
332 session.execute(update(User).where(User.id == user.id).values(birthdate=date(1988, 1, 1), gender="Woman"))
334 update_badges(empty_pb2.Empty())
335 refresh_materialized_views_rapid(empty_pb2.Empty())
337 with api_session(token) as api:
338 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
339 assert "strong_verification" not in res.badges
340 assert not res.has_strong_verification
341 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
342 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
343 assert (
344 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
345 == res.has_strong_verification
346 )
348 with account_session(token) as account:
349 res = account.GetAccountInfo(empty_pb2.Empty())
350 assert not res.has_strong_verification
351 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
352 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
354 # back to should have a badge
355 with session_scope() as session:
356 session.execute(update(User).where(User.id == user.id).values(gender="Man"))
358 update_badges(empty_pb2.Empty())
359 refresh_materialized_views_rapid(empty_pb2.Empty())
361 with api_session(token) as api:
362 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
363 assert "strong_verification" in res.badges
364 assert res.has_strong_verification
365 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
366 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
367 assert (
368 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
369 == res.has_strong_verification
370 )
371 with account_session(token) as account:
372 assert not any(
373 reminder.HasField("complete_verification_reminder")
374 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
375 )
377 # check has_passport_sex_gender_exception
378 with real_admin_session(superuser_token) as admin:
379 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
380 assert "strong_verification" in res.badges
381 assert res.has_strong_verification
382 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
383 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
385 admin.SetPassportSexGenderException(
386 admin_pb2.SetPassportSexGenderExceptionReq(user=user.username, passport_sex_gender_exception=True)
387 )
388 admin.ChangeUserGender(admin_pb2.ChangeUserGenderReq(user=user.username, gender="Woman"))
390 update_badges(empty_pb2.Empty())
391 refresh_materialized_views_rapid(empty_pb2.Empty())
393 with api_session(token) as api:
394 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
395 assert "strong_verification" in res.badges
396 assert res.has_strong_verification
397 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
398 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
399 assert (
400 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
401 == res.has_strong_verification
402 )
403 with account_session(token) as account:
404 assert not any(
405 reminder.HasField("complete_verification_reminder")
406 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
407 )
409 with real_admin_session(superuser_token) as admin:
410 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
411 assert "strong_verification" in res.badges
412 assert res.has_strong_verification
413 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
414 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
416 # now turn exception off
417 admin.SetPassportSexGenderException(
418 admin_pb2.SetPassportSexGenderExceptionReq(user=user.username, passport_sex_gender_exception=False)
419 )
421 update_badges(empty_pb2.Empty())
422 refresh_materialized_views_rapid(empty_pb2.Empty())
424 with api_session(token) as api:
425 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
426 assert "strong_verification" not in res.badges
427 assert not res.has_strong_verification
428 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
429 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
430 assert (
431 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
432 == res.has_strong_verification
433 )
434 with account_session(token) as account:
435 assert any(
436 reminder.HasField("complete_verification_reminder")
437 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
438 )
440 with real_admin_session(superuser_token) as admin:
441 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
442 assert "strong_verification" not in res.badges
443 assert not res.has_strong_verification
444 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
445 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
448def test_strong_verification_delete_data(db, monkeypatch):
449 monkeypatch_sv_config(monkeypatch)
451 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
452 _, superuser_token = generate_user(is_superuser=True)
454 refresh_materialized_views_rapid(empty_pb2.Empty())
456 with api_session(token) as api:
457 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
458 assert (
459 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
460 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
461 )
463 # can remove SV data even if there is none, should do nothing
464 with account_session(token) as account:
465 account.DeleteStrongVerificationData(empty_pb2.Empty())
467 do_and_check_sv(
468 user,
469 token,
470 verification_id=5731012934821983,
471 sex="MALE",
472 dob="1988-01-01",
473 document_type="PASSPORT",
474 document_number="31195855",
475 document_expiry=default_expiry,
476 nationality="US",
477 )
479 refresh_materialized_views_rapid(empty_pb2.Empty())
481 # the user should now have strong verification
482 with api_session(token) as api:
483 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
484 assert (
485 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
486 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
487 )
489 # check removing SV data
490 with account_session(token) as account:
491 account.DeleteStrongVerificationData(empty_pb2.Empty())
493 refresh_materialized_views_rapid(empty_pb2.Empty())
495 with api_session(token) as api:
496 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
497 assert (
498 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
499 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
500 )
502 with session_scope() as session:
503 assert (
504 len(
505 session.execute(
506 select(StrongVerificationAttempt).where(
507 or_(
508 StrongVerificationAttempt.passport_encrypted_data != None,
509 StrongVerificationAttempt.passport_date_of_birth != None,
510 StrongVerificationAttempt.passport_sex != None,
511 )
512 )
513 )
514 .scalars()
515 .all()
516 )
517 == 0
518 )
521def test_strong_verification_expiry(db, monkeypatch):
522 monkeypatch_sv_config(monkeypatch)
524 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
525 _, superuser_token = generate_user(is_superuser=True)
527 refresh_materialized_views_rapid(empty_pb2.Empty())
529 with api_session(token) as api:
530 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
531 assert (
532 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
533 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
534 )
536 expiry = date.today() + timedelta(days=10)
538 do_and_check_sv(
539 user,
540 token,
541 verification_id=5731012934821983,
542 sex="MALE",
543 dob="1988-01-01",
544 document_type="PASSPORT",
545 document_number="31195855",
546 document_expiry=expiry,
547 nationality="US",
548 )
550 # the user should now have strong verification
551 with api_session(token) as api:
552 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
553 assert res.has_strong_verification
554 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
555 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
557 with session_scope() as session:
558 attempt = session.execute(select(StrongVerificationAttempt)).scalars().one()
559 attempt.passport_expiry_date = date.today() - timedelta(days=2)
561 with api_session(token) as api:
562 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
563 assert not res.has_strong_verification
564 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_UNVERIFIED
565 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_UNVERIFIED
567 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
568 assert not res.has_strong_verification
569 assert not res.has_strong_verification
571 do_and_check_sv(
572 user,
573 token,
574 verification_id=5731012934821985,
575 sex="MALE",
576 dob="1988-01-01",
577 document_type="PASSPORT",
578 document_number="PA41323412",
579 document_expiry=date.today() + timedelta(days=365),
580 nationality="AU",
581 )
583 refresh_materialized_views_rapid(empty_pb2.Empty())
585 with api_session(token) as api:
586 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
587 assert (
588 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
589 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
590 )
593def test_strong_verification_regression(db, monkeypatch):
594 monkeypatch_sv_config(monkeypatch)
596 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
598 do_and_check_sv(
599 user,
600 token,
601 verification_id=5731012934821983,
602 sex="MALE",
603 dob="1988-01-01",
604 document_type="PASSPORT",
605 document_number="31195855",
606 document_expiry=default_expiry,
607 nationality="US",
608 return_after="INITIATED",
609 )
611 with api_session(token) as api:
612 api.Ping(api_pb2.PingReq())
615def test_strong_verification_regression2(db, monkeypatch):
616 monkeypatch_sv_config(monkeypatch)
618 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
620 do_and_check_sv(
621 user,
622 token,
623 verification_id=5731012934821983,
624 sex="MALE",
625 dob="1988-01-01",
626 document_type="PASSPORT",
627 document_number="31195855",
628 document_expiry=default_expiry,
629 nationality="US",
630 return_after="INITIATED",
631 )
633 do_and_check_sv(
634 user,
635 token,
636 verification_id=5731012934821985,
637 sex="MALE",
638 dob="1988-01-01",
639 document_type="PASSPORT",
640 document_number="PA41323412",
641 document_expiry=default_expiry,
642 nationality="AU",
643 )
645 refresh_materialized_views_rapid(empty_pb2.Empty())
647 with api_session(token) as api:
648 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
649 assert (
650 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
651 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
652 )
655def test_strong_verification_disabled(db):
656 user, token = generate_user()
658 with account_session(token) as account:
659 with pytest.raises(grpc.RpcError) as e:
660 account.InitiateStrongVerification(empty_pb2.Empty())
661 assert e.value.code() == grpc.StatusCode.UNAVAILABLE
662 assert e.value.details() == "Strong verification is currently disabled."
665def test_strong_verification_delete_data_cant_reverify(db, monkeypatch, push_collector: PushCollector):
666 monkeypatch_sv_config(monkeypatch)
668 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
669 _, superuser_token = generate_user(is_superuser=True)
671 refresh_materialized_views_rapid(empty_pb2.Empty())
673 with api_session(token) as api:
674 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
675 assert (
676 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
677 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
678 )
680 do_and_check_sv(
681 user,
682 token,
683 verification_id=5731012934821983,
684 sex="MALE",
685 dob="1988-01-01",
686 document_type="PASSPORT",
687 document_number="31195855",
688 document_expiry=default_expiry,
689 nationality="US",
690 )
692 refresh_materialized_views_rapid(empty_pb2.Empty())
694 # the user should now have strong verification
695 with api_session(token) as api:
696 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
697 assert (
698 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
699 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
700 )
702 # check removing SV data
703 with account_session(token) as account:
704 account.DeleteStrongVerificationData(empty_pb2.Empty())
706 refresh_materialized_views_rapid(empty_pb2.Empty())
708 with api_session(token) as api:
709 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
710 assert (
711 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
712 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
713 )
715 with session_scope() as session:
716 assert (
717 len(
718 session.execute(
719 select(StrongVerificationAttempt).where(
720 or_(
721 StrongVerificationAttempt.passport_encrypted_data != None,
722 StrongVerificationAttempt.passport_date_of_birth != None,
723 StrongVerificationAttempt.passport_sex != None,
724 )
725 )
726 )
727 .scalars()
728 .all()
729 )
730 == 0
731 )
733 reference_data = do_and_check_sv(
734 user,
735 token,
736 verification_id=5731012934821984,
737 sex="MALE",
738 dob="1988-01-01",
739 document_type="PASSPORT",
740 document_number="31195855",
741 document_expiry=default_expiry,
742 nationality="US",
743 return_after="APPROVED",
744 )
746 with patch("couchers.jobs.handlers.requests.post") as mock:
747 json_resp2 = {
748 "id": 5731012934821984,
749 "created": "2024-05-11T15:46:46Z",
750 "expires": "2024-05-11T16:17:26Z",
751 "state": "APPROVED",
752 "reference": reference_data,
753 "user_ip": "10.123.123.123",
754 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
755 "given_names": "John Wayne",
756 "surname": "Doe",
757 "nationality": "US",
758 "sex": "MALE",
759 "date_of_birth": "1988-01-01",
760 "document_type": "PASSPORT",
761 "document_number": "31195855",
762 "expiry_date": default_expiry.isoformat(),
763 "issuing_country": "US",
764 "issuer": "Department of State, U.S. Government",
765 "portrait": "dGVzdHRlc3R0ZXN0...",
766 }
767 mock.return_value = type(
768 "__MockResponse",
769 (),
770 {
771 "status_code": 200,
772 "text": json.dumps(json_resp2),
773 "json": lambda: json_resp2,
774 },
775 )
776 while process_job():
777 pass
779 mock.assert_called_once_with(
780 "https://passportreader.app/api/v1/session.get",
781 auth=("dummy_pubkey", "dummy_secret"),
782 json={"id": 5731012934821984},
783 timeout=10,
784 verify="/etc/ssl/certs/ca-certificates.crt",
785 )
787 with session_scope() as session:
788 verification_attempt = session.execute(
789 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
790 ).scalar_one()
791 assert verification_attempt.user_id == user.id
792 assert verification_attempt.status == StrongVerificationAttemptStatus.duplicate
794 push = push_collector.get_for_user(user.id, index=1)
795 assert push.content.title == "Strong Verification failed"
796 assert (
797 push.content.body
798 == "You used a passport that has already been used for verification. Please use another passport."
799 )
801 refresh_materialized_views_rapid(empty_pb2.Empty())
803 with api_session(token) as api:
804 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
805 assert (
806 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
807 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
808 )
811def test_strong_verification_duplicate_other_user(db, monkeypatch, push_collector: PushCollector):
812 monkeypatch_sv_config(monkeypatch)
814 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
815 user2, token2 = generate_user(birthdate=date(1988, 1, 1), gender="Man")
816 _, superuser_token = generate_user(is_superuser=True)
818 refresh_materialized_views_rapid(empty_pb2.Empty())
820 with api_session(token) as api:
821 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
822 assert (
823 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
824 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
825 )
827 # can remove SV data even if there is none, should do nothing
828 with account_session(token) as account:
829 account.DeleteStrongVerificationData(empty_pb2.Empty())
831 do_and_check_sv(
832 user,
833 token,
834 verification_id=5731012934821983,
835 sex="MALE",
836 dob="1988-01-01",
837 document_type="PASSPORT",
838 document_number="31195855",
839 document_expiry=default_expiry,
840 nationality="US",
841 )
843 refresh_materialized_views_rapid(empty_pb2.Empty())
845 # the user should now have strong verification
846 with api_session(token) as api:
847 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
848 assert (
849 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
850 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
851 )
853 # check removing SV data
854 with account_session(token) as account:
855 account.DeleteStrongVerificationData(empty_pb2.Empty())
857 refresh_materialized_views_rapid(empty_pb2.Empty())
859 with api_session(token) as api:
860 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
861 assert (
862 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
863 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
864 )
866 with session_scope() as session:
867 assert (
868 len(
869 session.execute(
870 select(StrongVerificationAttempt).where(
871 or_(
872 StrongVerificationAttempt.passport_encrypted_data != None,
873 StrongVerificationAttempt.passport_date_of_birth != None,
874 StrongVerificationAttempt.passport_sex != None,
875 )
876 )
877 )
878 .scalars()
879 .all()
880 )
881 == 0
882 )
884 reference_data = do_and_check_sv(
885 user2,
886 token2,
887 verification_id=5731012934821984,
888 sex="MALE",
889 dob="1988-01-01",
890 document_type="PASSPORT",
891 document_number="31195855",
892 document_expiry=default_expiry,
893 nationality="US",
894 return_after="APPROVED",
895 )
897 with patch("couchers.jobs.handlers.requests.post") as mock:
898 json_resp2 = {
899 "id": 5731012934821984,
900 "created": "2024-05-11T15:46:46Z",
901 "expires": "2024-05-11T16:17:26Z",
902 "state": "APPROVED",
903 "reference": reference_data,
904 "user_ip": "10.123.123.123",
905 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
906 "given_names": "John Wayne",
907 "surname": "Doe",
908 "nationality": "US",
909 "sex": "MALE",
910 "date_of_birth": "1988-01-01",
911 "document_type": "PASSPORT",
912 "document_number": "31195855",
913 "expiry_date": default_expiry.isoformat(),
914 "issuing_country": "US",
915 "issuer": "Department of State, U.S. Government",
916 "portrait": "dGVzdHRlc3R0ZXN0...",
917 }
918 mock.return_value = type(
919 "__MockResponse",
920 (),
921 {
922 "status_code": 200,
923 "text": json.dumps(json_resp2),
924 "json": lambda: json_resp2,
925 },
926 )
927 while process_job():
928 pass
930 mock.assert_called_once_with(
931 "https://passportreader.app/api/v1/session.get",
932 auth=("dummy_pubkey", "dummy_secret"),
933 json={"id": 5731012934821984},
934 timeout=10,
935 verify="/etc/ssl/certs/ca-certificates.crt",
936 )
938 with session_scope() as session:
939 verification_attempt = session.execute(
940 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
941 ).scalar_one()
942 assert verification_attempt.user_id == user2.id
943 assert verification_attempt.status == StrongVerificationAttemptStatus.duplicate
945 push = push_collector.get_for_user(user2.id, index=0)
946 assert push.content.title == "Strong Verification failed"
947 assert (
948 push.content.body
949 == "You used a passport that has already been used for verification. Please use another passport."
950 )
953def test_strong_verification_non_passport(db, monkeypatch, push_collector: PushCollector):
954 monkeypatch_sv_config(monkeypatch)
956 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
957 _, superuser_token = generate_user(is_superuser=True)
959 reference_data = do_and_check_sv(
960 user,
961 token,
962 verification_id=5731012934821984,
963 sex="MALE",
964 dob="1988-01-01",
965 document_type="IDENTITY_CARD",
966 document_number="31195855",
967 document_expiry=default_expiry,
968 nationality="US",
969 return_after="APPROVED",
970 )
972 with patch("couchers.jobs.handlers.requests.post") as mock:
973 json_resp2 = {
974 "id": 5731012934821984,
975 "created": "2024-05-11T15:46:46Z",
976 "expires": "2024-05-11T16:17:26Z",
977 "state": "APPROVED",
978 "reference": reference_data,
979 "user_ip": "10.123.123.123",
980 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
981 "given_names": "John Wayne",
982 "surname": "Doe",
983 "nationality": "US",
984 "sex": "MALE",
985 "date_of_birth": "1988-01-01",
986 "document_type": "IDENTITY_CARD",
987 "document_number": "31195855",
988 "expiry_date": default_expiry.isoformat(),
989 "issuing_country": "US",
990 "issuer": "Department of State, U.S. Government",
991 "portrait": "dGVzdHRlc3R0ZXN0...",
992 }
993 mock.return_value = type(
994 "__MockResponse",
995 (),
996 {
997 "status_code": 200,
998 "text": json.dumps(json_resp2),
999 "json": lambda: json_resp2,
1000 },
1001 )
1002 while process_job():
1003 pass
1005 mock.assert_called_once_with(
1006 "https://passportreader.app/api/v1/session.get",
1007 auth=("dummy_pubkey", "dummy_secret"),
1008 json={"id": 5731012934821984},
1009 timeout=10,
1010 verify="/etc/ssl/certs/ca-certificates.crt",
1011 )
1013 with session_scope() as session:
1014 verification_attempt = session.execute(
1015 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
1016 ).scalar_one()
1017 assert verification_attempt.user_id == user.id
1018 assert verification_attempt.status == StrongVerificationAttemptStatus.failed
1020 push = push_collector.get_for_user(user.id, index=0)
1021 assert push.content.title == "Strong Verification failed"
1022 assert (
1023 push.content.body
1024 == "You used a document other than a passport. You can only use a passport for Strong Verification."
1025 )