Coverage for src/tests/test_strong_verification.py: 99%
377 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-09 20:28 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-09 20:28 +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 "passport_only": True,
104 "reference": ANY,
105 },
106 timeout=10,
107 verify="/etc/ssl/certs/ca-certificates.crt",
108 )
109 reference_data = mock.call_args.kwargs["json"]["reference"]
110 verification_attempt_token = res.verification_attempt_token
111 return_url = f"http://localhost:3000/complete-strong-verification?verification_attempt_token={verification_attempt_token}"
112 assert res.redirect_url == "https://passportreader.app/open?" + urlencode(
113 {"token": iris_token, "redirect_url": return_url}
114 )
116 assert (
117 account.GetStrongVerificationAttemptStatus(
118 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
119 ).status
120 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_USER_TO_OPEN_APP
121 )
123 # ok, now the user downloads the app, scans their id, and Iris ID sends callbacks to the server
124 _emulate_iris_callback(verification_id, "INITIATED", reference_data)
126 with account_session(token) as account:
127 assert (
128 account.GetStrongVerificationAttemptStatus(
129 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
130 ).status
131 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_USER_IN_APP
132 )
134 if return_after == "INITIATED":
135 return reference_data
137 _emulate_iris_callback(verification_id, "COMPLETED", reference_data)
139 with account_session(token) as account:
140 assert (
141 account.GetStrongVerificationAttemptStatus(
142 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
143 ).status
144 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_BACKEND
145 )
147 if return_after == "COMPLETED":
148 return reference_data
150 _emulate_iris_callback(verification_id, "APPROVED", reference_data)
152 with account_session(token) as account:
153 assert (
154 account.GetStrongVerificationAttemptStatus(
155 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
156 ).status
157 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_IN_PROGRESS_WAITING_ON_BACKEND
158 )
160 if return_after == "APPROVED":
161 return reference_data
163 with patch("couchers.jobs.handlers.requests.post") as mock:
164 json_resp2 = {
165 "id": verification_id,
166 "created": "2024-05-11T15:46:46Z",
167 "expires": "2024-05-11T16:17:26Z",
168 "state": "APPROVED",
169 "reference": reference_data,
170 "user_ip": "10.123.123.123",
171 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
172 "given_names": "John Wayne",
173 "surname": "Doe",
174 "nationality": nationality,
175 "sex": sex,
176 "date_of_birth": dob,
177 "document_type": document_type,
178 "document_number": document_number,
179 "expiry_date": document_expiry.isoformat(),
180 "issuing_country": nationality,
181 "issuer": "Department of State, U.S. Government",
182 "portrait": "dGVzdHRlc3R0ZXN0...",
183 }
184 mock.return_value = type(
185 "__MockResponse",
186 (),
187 {
188 "status_code": 200,
189 "text": json.dumps(json_resp2),
190 "json": lambda: json_resp2,
191 },
192 )
193 while process_job():
194 pass
196 mock.assert_called_once_with(
197 "https://passportreader.app/api/v1/session.get",
198 auth=("dummy_pubkey", "dummy_secret"),
199 json={"id": verification_id},
200 timeout=10,
201 verify="/etc/ssl/certs/ca-certificates.crt",
202 )
204 with account_session(token) as account:
205 assert (
206 account.GetStrongVerificationAttemptStatus(
207 account_pb2.GetStrongVerificationAttemptStatusReq(verification_attempt_token=verification_attempt_token)
208 ).status
209 == account_pb2.STRONG_VERIFICATION_ATTEMPT_STATUS_SUCCEEDED
210 )
212 with session_scope() as session:
213 verification_attempt = session.execute(
214 select(StrongVerificationAttempt).where(
215 StrongVerificationAttempt.verification_attempt_token == verification_attempt_token
216 )
217 ).scalar_one()
218 assert verification_attempt.user_id == user.id
219 assert verification_attempt.status == StrongVerificationAttemptStatus.succeeded
220 assert verification_attempt.has_full_data
221 assert verification_attempt.passport_encrypted_data
222 # assert verification_attempt.passport_date_of_birth == date(1988, 1, 1)
223 # assert verification_attempt.passport_sex == PassportSex.male
224 assert verification_attempt.has_minimal_data
225 assert verification_attempt.passport_expiry_date == document_expiry
226 assert verification_attempt.passport_nationality == nationality
227 assert verification_attempt.passport_last_three_document_chars == document_number[-3:]
228 assert verification_attempt.iris_token == iris_token
229 assert verification_attempt.iris_session_id == verification_id
231 private_key = bytes.fromhex("e6c2fbf3756b387bc09a458a7b85935718ef3eb1c2777ef41d335c9f6c0ab272")
232 decrypted_data = json.loads(asym_decrypt(private_key, verification_attempt.passport_encrypted_data))
233 assert decrypted_data == json_resp2
235 callbacks = (
236 session.execute(
237 select(StrongVerificationCallbackEvent.iris_status)
238 .where(StrongVerificationCallbackEvent.verification_attempt_id == verification_attempt.id)
239 .order_by(StrongVerificationCallbackEvent.created.asc())
240 )
241 .scalars()
242 .all()
243 )
244 assert callbacks == ["INITIATED", "COMPLETED", "APPROVED"]
247def monkeypatch_sv_config(monkeypatch):
248 new_config = config.copy()
249 new_config["ENABLE_STRONG_VERIFICATION"] = True
250 new_config["IRIS_ID_PUBKEY"] = "dummy_pubkey"
251 new_config["IRIS_ID_SECRET"] = "dummy_secret"
252 new_config["VERIFICATION_DATA_PUBLIC_KEY"] = bytes.fromhex(
253 "dd740a2b2a35bf05041a28257ea439b30f76f056f3698000b71e6470cd82275f"
254 )
256 private_key = bytes.fromhex("e6c2fbf3756b387bc09a458a7b85935718ef3eb1c2777ef41d335c9f6c0ab272")
258 monkeypatch.setattr(couchers.servicers.account, "config", new_config)
259 monkeypatch.setattr(couchers.jobs.handlers, "config", new_config)
262def test_strong_verification_happy_path(db, monkeypatch):
263 monkeypatch_sv_config(monkeypatch)
265 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
266 _, superuser_token = generate_user(is_superuser=True)
268 update_badges(empty_pb2.Empty())
269 refresh_materialized_views_rapid(None)
271 with api_session(token) as api:
272 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
273 assert "strong_verification" not in res.badges
274 assert not res.has_strong_verification
275 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_UNVERIFIED
276 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_UNVERIFIED
277 assert (
278 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
279 == res.has_strong_verification
280 )
282 do_and_check_sv(
283 user,
284 token,
285 verification_id=5731012934821983,
286 sex="MALE",
287 dob="1988-01-01",
288 document_type="PASSPORT",
289 document_number="31195855",
290 document_expiry=default_expiry,
291 nationality="US",
292 )
294 with session_scope() as session:
295 verification_attempt = session.execute(
296 select(StrongVerificationAttempt).where(StrongVerificationAttempt.user_id == user.id)
297 ).scalar_one()
298 assert verification_attempt.status == StrongVerificationAttemptStatus.succeeded
299 assert verification_attempt.passport_date_of_birth == date(1988, 1, 1)
300 assert verification_attempt.passport_sex == PassportSex.male
301 assert verification_attempt.passport_expiry_date == default_expiry
302 assert verification_attempt.passport_nationality == "US"
303 assert verification_attempt.passport_last_three_document_chars == "855"
305 update_badges(empty_pb2.Empty())
306 refresh_materialized_views_rapid(None)
308 # the user should now have strong verification
309 with api_session(token) as api:
310 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
311 assert "strong_verification" in res.badges
312 assert res.has_strong_verification
313 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
314 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
315 assert (
316 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
317 == res.has_strong_verification
318 )
320 # wrong dob = no badge
321 with session_scope() as session:
322 user_ = session.execute(select(User).where(User.id == user.id)).scalar_one()
323 user_.birthdate = date(1988, 1, 2)
325 update_badges(empty_pb2.Empty())
326 refresh_materialized_views_rapid(None)
328 with api_session(token) as api:
329 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
330 assert "strong_verification" not in res.badges
331 assert not res.has_strong_verification
332 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_MISMATCH
333 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
334 assert (
335 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
336 == res.has_strong_verification
337 )
339 # bad gender-sex correspondence = no badge
340 with session_scope() as session:
341 user_ = session.execute(select(User).where(User.id == user.id)).scalar_one()
342 user_.birthdate = date(1988, 1, 1)
343 user_.gender = "Woman"
345 update_badges(empty_pb2.Empty())
346 refresh_materialized_views_rapid(None)
348 with api_session(token) as api:
349 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
350 assert "strong_verification" not in res.badges
351 assert not res.has_strong_verification
352 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
353 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
354 assert (
355 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
356 == res.has_strong_verification
357 )
359 with account_session(token) as account:
360 res = account.GetAccountInfo(empty_pb2.Empty())
361 assert not res.has_strong_verification
362 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
363 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
365 # back to should have a badge
366 with session_scope() as session:
367 user_ = session.execute(select(User).where(User.id == user.id)).scalar_one()
368 user_.gender = "Man"
370 update_badges(empty_pb2.Empty())
371 refresh_materialized_views_rapid(None)
373 with api_session(token) as api:
374 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
375 assert "strong_verification" in res.badges
376 assert res.has_strong_verification
377 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
378 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
379 assert (
380 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
381 == res.has_strong_verification
382 )
383 with account_session(token) as account:
384 assert not any(
385 reminder.HasField("complete_verification_reminder")
386 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
387 )
389 # check has_passport_sex_gender_exception
390 with real_admin_session(superuser_token) as admin:
391 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
392 assert "strong_verification" in res.badges
393 assert res.has_strong_verification
394 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
395 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
397 admin.SetPassportSexGenderException(
398 admin_pb2.SetPassportSexGenderExceptionReq(user=user.username, passport_sex_gender_exception=True)
399 )
400 admin.ChangeUserGender(admin_pb2.ChangeUserGenderReq(user=user.username, gender="Woman"))
402 update_badges(empty_pb2.Empty())
403 refresh_materialized_views_rapid(None)
405 with api_session(token) as api:
406 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
407 assert "strong_verification" in res.badges
408 assert res.has_strong_verification
409 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
410 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
411 assert (
412 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
413 == res.has_strong_verification
414 )
415 with account_session(token) as account:
416 assert not any(
417 reminder.HasField("complete_verification_reminder")
418 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
419 )
421 with real_admin_session(superuser_token) as admin:
422 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
423 assert "strong_verification" in res.badges
424 assert res.has_strong_verification
425 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
426 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
428 # now turn exception off
429 admin.SetPassportSexGenderException(
430 admin_pb2.SetPassportSexGenderExceptionReq(user=user.username, passport_sex_gender_exception=False)
431 )
433 update_badges(empty_pb2.Empty())
434 refresh_materialized_views_rapid(None)
436 with api_session(token) as api:
437 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
438 assert "strong_verification" not in res.badges
439 assert not res.has_strong_verification
440 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
441 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
442 assert (
443 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
444 == res.has_strong_verification
445 )
446 with account_session(token) as account:
447 assert any(
448 reminder.HasField("complete_verification_reminder")
449 for reminder in account.GetReminders(empty_pb2.Empty()).reminders
450 )
452 with real_admin_session(superuser_token) as admin:
453 res = admin.GetUserDetails(admin_pb2.GetUserDetailsReq(user=user.username))
454 assert "strong_verification" not in res.badges
455 assert not res.has_strong_verification
456 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
457 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_MISMATCH
460def test_strong_verification_delete_data(db, monkeypatch):
461 monkeypatch_sv_config(monkeypatch)
463 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
464 _, superuser_token = generate_user(is_superuser=True)
466 refresh_materialized_views_rapid(None)
468 with api_session(token) as api:
469 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
470 assert (
471 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
472 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
473 )
475 # can remove SV data even if there is none, should do nothing
476 with account_session(token) as account:
477 account.DeleteStrongVerificationData(empty_pb2.Empty())
479 do_and_check_sv(
480 user,
481 token,
482 verification_id=5731012934821983,
483 sex="MALE",
484 dob="1988-01-01",
485 document_type="PASSPORT",
486 document_number="31195855",
487 document_expiry=default_expiry,
488 nationality="US",
489 )
491 refresh_materialized_views_rapid(None)
493 # the user should now have strong verification
494 with api_session(token) as api:
495 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
496 assert (
497 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
498 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
499 )
501 # check removing SV data
502 with account_session(token) as account:
503 account.DeleteStrongVerificationData(empty_pb2.Empty())
505 refresh_materialized_views_rapid(None)
507 with api_session(token) as api:
508 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
509 assert (
510 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
511 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
512 )
514 with session_scope() as session:
515 assert (
516 len(
517 session.execute(
518 select(StrongVerificationAttempt).where(
519 or_(
520 StrongVerificationAttempt.passport_encrypted_data != None,
521 StrongVerificationAttempt.passport_date_of_birth != None,
522 StrongVerificationAttempt.passport_sex != None,
523 )
524 )
525 )
526 .scalars()
527 .all()
528 )
529 == 0
530 )
533def test_strong_verification_expiry(db, monkeypatch):
534 monkeypatch_sv_config(monkeypatch)
536 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
537 _, superuser_token = generate_user(is_superuser=True)
539 refresh_materialized_views_rapid(None)
541 with api_session(token) as api:
542 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
543 assert (
544 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
545 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
546 )
548 expiry = date.today() + timedelta(days=10)
550 do_and_check_sv(
551 user,
552 token,
553 verification_id=5731012934821983,
554 sex="MALE",
555 dob="1988-01-01",
556 document_type="PASSPORT",
557 document_number="31195855",
558 document_expiry=expiry,
559 nationality="US",
560 )
562 # the user should now have strong verification
563 with api_session(token) as api:
564 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
565 assert res.has_strong_verification
566 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_VERIFIED
567 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_VERIFIED
569 with session_scope() as session:
570 attempt = session.execute(select(StrongVerificationAttempt)).scalars().one()
571 attempt.passport_expiry_date = date.today() - timedelta(days=2)
573 with api_session(token) as api:
574 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
575 assert not res.has_strong_verification
576 assert res.birthdate_verification_status == api_pb2.BIRTHDATE_VERIFICATION_STATUS_UNVERIFIED
577 assert res.gender_verification_status == api_pb2.GENDER_VERIFICATION_STATUS_UNVERIFIED
579 res = api.GetUser(api_pb2.GetUserReq(user=user.username))
580 assert not res.has_strong_verification
581 assert not res.has_strong_verification
583 do_and_check_sv(
584 user,
585 token,
586 verification_id=5731012934821985,
587 sex="MALE",
588 dob="1988-01-01",
589 document_type="PASSPORT",
590 document_number="PA41323412",
591 document_expiry=date.today() + timedelta(days=365),
592 nationality="AU",
593 )
595 refresh_materialized_views_rapid(None)
597 with api_session(token) as api:
598 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
599 assert (
600 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
601 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
602 )
605def test_strong_verification_regression(db, monkeypatch):
606 monkeypatch_sv_config(monkeypatch)
608 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
610 do_and_check_sv(
611 user,
612 token,
613 verification_id=5731012934821983,
614 sex="MALE",
615 dob="1988-01-01",
616 document_type="PASSPORT",
617 document_number="31195855",
618 document_expiry=default_expiry,
619 nationality="US",
620 return_after="INITIATED",
621 )
623 with api_session(token) as api:
624 api.Ping(api_pb2.PingReq())
627def test_strong_verification_regression2(db, monkeypatch):
628 monkeypatch_sv_config(monkeypatch)
630 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
632 do_and_check_sv(
633 user,
634 token,
635 verification_id=5731012934821983,
636 sex="MALE",
637 dob="1988-01-01",
638 document_type="PASSPORT",
639 document_number="31195855",
640 document_expiry=default_expiry,
641 nationality="US",
642 return_after="INITIATED",
643 )
645 do_and_check_sv(
646 user,
647 token,
648 verification_id=5731012934821985,
649 sex="MALE",
650 dob="1988-01-01",
651 document_type="PASSPORT",
652 document_number="PA41323412",
653 document_expiry=default_expiry,
654 nationality="AU",
655 )
657 refresh_materialized_views_rapid(None)
659 with api_session(token) as api:
660 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
661 assert (
662 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
663 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
664 )
667def test_strong_verification_disabled(db):
668 user, token = generate_user()
670 with account_session(token) as account:
671 with pytest.raises(grpc.RpcError) as e:
672 account.InitiateStrongVerification(empty_pb2.Empty())
673 assert e.value.code() == grpc.StatusCode.UNAVAILABLE
674 assert e.value.details() == errors.STRONG_VERIFICATION_DISABLED
677def test_strong_verification_delete_data_cant_reverify(db, monkeypatch, push_collector):
678 monkeypatch_sv_config(monkeypatch)
680 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
681 _, superuser_token = generate_user(is_superuser=True)
683 refresh_materialized_views_rapid(None)
685 with api_session(token) as api:
686 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
687 assert (
688 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
689 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
690 )
692 do_and_check_sv(
693 user,
694 token,
695 verification_id=5731012934821983,
696 sex="MALE",
697 dob="1988-01-01",
698 document_type="PASSPORT",
699 document_number="31195855",
700 document_expiry=default_expiry,
701 nationality="US",
702 )
704 refresh_materialized_views_rapid(None)
706 # the user should now have strong verification
707 with api_session(token) as api:
708 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
709 assert (
710 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
711 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
712 )
714 # check removing SV data
715 with account_session(token) as account:
716 account.DeleteStrongVerificationData(empty_pb2.Empty())
718 refresh_materialized_views_rapid(None)
720 with api_session(token) as api:
721 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
722 assert (
723 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
724 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
725 )
727 with session_scope() as session:
728 assert (
729 len(
730 session.execute(
731 select(StrongVerificationAttempt).where(
732 or_(
733 StrongVerificationAttempt.passport_encrypted_data != None,
734 StrongVerificationAttempt.passport_date_of_birth != None,
735 StrongVerificationAttempt.passport_sex != None,
736 )
737 )
738 )
739 .scalars()
740 .all()
741 )
742 == 0
743 )
745 reference_data = do_and_check_sv(
746 user,
747 token,
748 verification_id=5731012934821984,
749 sex="MALE",
750 dob="1988-01-01",
751 document_type="PASSPORT",
752 document_number="31195855",
753 document_expiry=default_expiry,
754 nationality="US",
755 return_after="APPROVED",
756 )
758 with patch("couchers.jobs.handlers.requests.post") as mock:
759 json_resp2 = {
760 "id": 5731012934821984,
761 "created": "2024-05-11T15:46:46Z",
762 "expires": "2024-05-11T16:17:26Z",
763 "state": "APPROVED",
764 "reference": reference_data,
765 "user_ip": "10.123.123.123",
766 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
767 "given_names": "John Wayne",
768 "surname": "Doe",
769 "nationality": "US",
770 "sex": "MALE",
771 "date_of_birth": "1988-01-01",
772 "document_type": "PASSPORT",
773 "document_number": "31195855",
774 "expiry_date": default_expiry.isoformat(),
775 "issuing_country": "US",
776 "issuer": "Department of State, U.S. Government",
777 "portrait": "dGVzdHRlc3R0ZXN0...",
778 }
779 mock.return_value = type(
780 "__MockResponse",
781 (),
782 {
783 "status_code": 200,
784 "text": json.dumps(json_resp2),
785 "json": lambda: json_resp2,
786 },
787 )
788 while process_job():
789 pass
791 mock.assert_called_once_with(
792 "https://passportreader.app/api/v1/session.get",
793 auth=("dummy_pubkey", "dummy_secret"),
794 json={"id": 5731012934821984},
795 timeout=10,
796 verify="/etc/ssl/certs/ca-certificates.crt",
797 )
799 with session_scope() as session:
800 verification_attempt = session.execute(
801 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
802 ).scalar_one()
803 assert verification_attempt.user_id == user.id
804 assert verification_attempt.status == StrongVerificationAttemptStatus.duplicate
806 push_collector.assert_user_push_matches_fields(
807 user.id,
808 ix=1,
809 title="Strong Verification failed",
810 body="You tried to verify with a passport that has already been used for verification. Please use another passport.",
811 )
813 refresh_materialized_views_rapid(None)
815 with api_session(token) as api:
816 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
817 assert (
818 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
819 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
820 )
823def test_strong_verification_duplicate_other_user(db, monkeypatch, push_collector):
824 monkeypatch_sv_config(monkeypatch)
826 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
827 user2, token2 = generate_user(birthdate=date(1988, 1, 1), gender="Man")
828 _, superuser_token = generate_user(is_superuser=True)
830 refresh_materialized_views_rapid(None)
832 with api_session(token) as api:
833 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
834 assert (
835 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
836 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
837 )
839 # can remove SV data even if there is none, should do nothing
840 with account_session(token) as account:
841 account.DeleteStrongVerificationData(empty_pb2.Empty())
843 do_and_check_sv(
844 user,
845 token,
846 verification_id=5731012934821983,
847 sex="MALE",
848 dob="1988-01-01",
849 document_type="PASSPORT",
850 document_number="31195855",
851 document_expiry=default_expiry,
852 nationality="US",
853 )
855 refresh_materialized_views_rapid(None)
857 # the user should now have strong verification
858 with api_session(token) as api:
859 assert api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
860 assert (
861 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
862 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
863 )
865 # check removing SV data
866 with account_session(token) as account:
867 account.DeleteStrongVerificationData(empty_pb2.Empty())
869 refresh_materialized_views_rapid(None)
871 with api_session(token) as api:
872 assert not api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
873 assert (
874 api.GetLiteUser(api_pb2.GetLiteUserReq(user=user.username)).has_strong_verification
875 == api.GetUser(api_pb2.GetUserReq(user=user.username)).has_strong_verification
876 )
878 with session_scope() as session:
879 assert (
880 len(
881 session.execute(
882 select(StrongVerificationAttempt).where(
883 or_(
884 StrongVerificationAttempt.passport_encrypted_data != None,
885 StrongVerificationAttempt.passport_date_of_birth != None,
886 StrongVerificationAttempt.passport_sex != None,
887 )
888 )
889 )
890 .scalars()
891 .all()
892 )
893 == 0
894 )
896 reference_data = do_and_check_sv(
897 user2,
898 token2,
899 verification_id=5731012934821984,
900 sex="MALE",
901 dob="1988-01-01",
902 document_type="PASSPORT",
903 document_number="31195855",
904 document_expiry=default_expiry,
905 nationality="US",
906 return_after="APPROVED",
907 )
909 with patch("couchers.jobs.handlers.requests.post") as mock:
910 json_resp2 = {
911 "id": 5731012934821984,
912 "created": "2024-05-11T15:46:46Z",
913 "expires": "2024-05-11T16:17:26Z",
914 "state": "APPROVED",
915 "reference": reference_data,
916 "user_ip": "10.123.123.123",
917 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
918 "given_names": "John Wayne",
919 "surname": "Doe",
920 "nationality": "US",
921 "sex": "MALE",
922 "date_of_birth": "1988-01-01",
923 "document_type": "PASSPORT",
924 "document_number": "31195855",
925 "expiry_date": default_expiry.isoformat(),
926 "issuing_country": "US",
927 "issuer": "Department of State, U.S. Government",
928 "portrait": "dGVzdHRlc3R0ZXN0...",
929 }
930 mock.return_value = type(
931 "__MockResponse",
932 (),
933 {
934 "status_code": 200,
935 "text": json.dumps(json_resp2),
936 "json": lambda: json_resp2,
937 },
938 )
939 while process_job():
940 pass
942 mock.assert_called_once_with(
943 "https://passportreader.app/api/v1/session.get",
944 auth=("dummy_pubkey", "dummy_secret"),
945 json={"id": 5731012934821984},
946 timeout=10,
947 verify="/etc/ssl/certs/ca-certificates.crt",
948 )
950 with session_scope() as session:
951 verification_attempt = session.execute(
952 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
953 ).scalar_one()
954 assert verification_attempt.user_id == user2.id
955 assert verification_attempt.status == StrongVerificationAttemptStatus.duplicate
957 push_collector.assert_user_push_matches_fields(
958 user2.id,
959 title="Strong Verification failed",
960 body="You tried to verify with a passport that has already been used for verification. Please use another passport.",
961 )
964def test_strong_verification_non_passport(db, monkeypatch, push_collector):
965 monkeypatch_sv_config(monkeypatch)
967 user, token = generate_user(birthdate=date(1988, 1, 1), gender="Man")
968 _, superuser_token = generate_user(is_superuser=True)
970 reference_data = do_and_check_sv(
971 user,
972 token,
973 verification_id=5731012934821984,
974 sex="MALE",
975 dob="1988-01-01",
976 document_type="IDENTITY_CARD",
977 document_number="31195855",
978 document_expiry=default_expiry,
979 nationality="US",
980 return_after="APPROVED",
981 )
983 with patch("couchers.jobs.handlers.requests.post") as mock:
984 json_resp2 = {
985 "id": 5731012934821984,
986 "created": "2024-05-11T15:46:46Z",
987 "expires": "2024-05-11T16:17:26Z",
988 "state": "APPROVED",
989 "reference": reference_data,
990 "user_ip": "10.123.123.123",
991 "user_agent": "Iris%20ID/168357896 CFNetwork/1494.0.7 Darwin/23.4.0",
992 "given_names": "John Wayne",
993 "surname": "Doe",
994 "nationality": "US",
995 "sex": "MALE",
996 "date_of_birth": "1988-01-01",
997 "document_type": "IDENTITY_CARD",
998 "document_number": "31195855",
999 "expiry_date": default_expiry.isoformat(),
1000 "issuing_country": "US",
1001 "issuer": "Department of State, U.S. Government",
1002 "portrait": "dGVzdHRlc3R0ZXN0...",
1003 }
1004 mock.return_value = type(
1005 "__MockResponse",
1006 (),
1007 {
1008 "status_code": 200,
1009 "text": json.dumps(json_resp2),
1010 "json": lambda: json_resp2,
1011 },
1012 )
1013 while process_job():
1014 pass
1016 mock.assert_called_once_with(
1017 "https://passportreader.app/api/v1/session.get",
1018 auth=("dummy_pubkey", "dummy_secret"),
1019 json={"id": 5731012934821984},
1020 timeout=10,
1021 verify="/etc/ssl/certs/ca-certificates.crt",
1022 )
1024 with session_scope() as session:
1025 verification_attempt = session.execute(
1026 select(StrongVerificationAttempt).where(StrongVerificationAttempt.iris_session_id == 5731012934821984)
1027 ).scalar_one()
1028 assert verification_attempt.user_id == user.id
1029 assert verification_attempt.status == StrongVerificationAttemptStatus.failed
1031 push_collector.assert_user_push_matches_fields(
1032 user.id,
1033 title="Strong Verification failed",
1034 body="You tried to verify with a document that is not a passport. You can only use a passport for Strong Verification.",
1035 )