Skip to content

Commit b689701

Browse files
fix: validate computed field to ensure reject expired documents
1 parent a08f78f commit b689701

File tree

6 files changed

+387
-25
lines changed

6 files changed

+387
-25
lines changed

mati/resources/verifications.py

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,7 @@ def govt_id_document(self) -> Optional[VerificationDocument]:
111111

112112
@property
113113
def proof_of_residency_validation(self) -> Optional[DocumentScore]:
114-
por = self.proof_of_residency_document
115-
if not por:
116-
return None
117-
return DocumentScore(
118-
all([step.status == 200 and not step.error for step in por.steps])
119-
and not (
120-
self.computed
121-
and self.computed['is_document_expired']['data'][
122-
'proof_of_residency'
123-
]
124-
),
125-
sum([step.status for step in por.steps if not step.error]),
126-
[step.error['code'] for step in por.steps if step.error],
127-
)
114+
return self.get_document_validation(self.proof_of_residency_document)
128115

129116
@property
130117
def proof_of_life_validation(self) -> Optional[DocumentScore]:
@@ -139,13 +126,32 @@ def proof_of_life_validation(self) -> Optional[DocumentScore]:
139126

140127
@property
141128
def govt_id_validation(self) -> Optional[DocumentScore]:
142-
govt = self.govt_id_document
143-
if not govt:
144-
return None
145-
return DocumentScore(
146-
all(
147-
[step.status == 200 and not step.error for step in govt.steps]
148-
),
149-
sum([step.status for step in govt.steps if not step.error]),
150-
[step.error['code'] for step in govt.steps if step.error],
129+
return self.get_document_validation(self.govt_id_document)
130+
131+
132+
def get_document_validation(
133+
self,
134+
document: Optional[VerificationDocument]
135+
) -> Optional[DocumentScore]:
136+
if not document:
137+
return None
138+
type = document.type.replace("-", "_")
139+
is_expired = (
140+
self.computed['is_document_expired']['data'][type]
141+
if self.computed
142+
else False
151143
)
144+
steps = document.steps
145+
if is_expired:
146+
steps.append(VerificationDocumentStep(
147+
id=f'{type}_verification',
148+
status=500,
149+
error={
150+
'verification': f'Document {type} expired', 'code': 500
151+
})
152+
)
153+
return DocumentScore(
154+
all([step.status == 200 and not step.error for step in steps]),
155+
sum([step.status for step in steps if not step.error]),
156+
[step.error['code'] for step in steps if step.error],
157+
)

mati/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.0.6'
1+
__version__ = '2.0.7.dev1'

tests/conftest.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def vcr_config() -> dict:
209209

210210
@pytest.fixture
211211
def client() -> Generator:
212-
yield Client('api_key', 'secret_key')
212+
yield Client()
213213

214214

215215
@pytest.fixture
@@ -227,6 +227,19 @@ def verification_without_pol(client: Client):
227227
verification.steps = []
228228
yield verification
229229

230+
@pytest.fixture
231+
def verification_with_govt_expired(client: Client):
232+
verification = client.verifications.retrieve('686c77811ee936aece7016ac')
233+
verification.computed["is_document_expired"]["data"]["national_id"] = True
234+
yield verification
235+
236+
237+
@pytest.fixture
238+
def verification_with_poa_expired(client: Client):
239+
verification = client.verifications.retrieve('686c77811ee936aece7016ac')
240+
verification.computed["is_document_expired"]["data"]["proof_of_residency"] = True
241+
yield verification
242+
230243

231244
@pytest.fixture
232245
def verification_document_national_id() -> VerificationDocument:
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
interactions:
2+
- request:
3+
body: grant_type=client_credentials
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Content-Length:
12+
- '29'
13+
Content-Type:
14+
- application/x-www-form-urlencoded
15+
User-Agent:
16+
- mati-python/2.0.7.dev1
17+
method: POST
18+
uri: https://api.getmati.com/oauth
19+
response:
20+
body:
21+
string: '{"access_token": "ACCESS_TOKEN", "expiresIn": 3600, "payload": {"user":
22+
{"_id": "ID", "firstName": "FIRST_NAME", "lastName": "LAST_NAME"}}}'
23+
headers:
24+
Connection:
25+
- keep-alive
26+
Content-Type:
27+
- application/json; charset=utf-8
28+
Date:
29+
- Tue, 08 Jul 2025 23:18:57 GMT
30+
Transfer-Encoding:
31+
- chunked
32+
cache-control:
33+
- no-store, no-cache, must-revalidate, proxy-revalidate
34+
content-security-policy:
35+
- 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self''
36+
https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src
37+
''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests'
38+
expect-ct:
39+
- max-age=0
40+
expires:
41+
- '0'
42+
pragma:
43+
- no-cache
44+
referrer-policy:
45+
- no-referrer
46+
strict-transport-security:
47+
- max-age=15552000; includeSubDomains; preload
48+
surrogate-control:
49+
- no-store
50+
x-content-type-options:
51+
- nosniff
52+
x-dns-prefetch-control:
53+
- 'off'
54+
x-download-options:
55+
- noopen
56+
x-envoy-upstream-service-time:
57+
- '141'
58+
x-frame-options:
59+
- DENY
60+
x-permitted-cross-domain-policies:
61+
- none
62+
x-request-id:
63+
- add8dd4e-74a3-414d-83aa-f8e1dd998672
64+
x-xss-protection:
65+
- '1'
66+
status:
67+
code: 200
68+
message: OK
69+
- request:
70+
body: null
71+
headers:
72+
Accept:
73+
- '*/*'
74+
Accept-Encoding:
75+
- gzip, deflate
76+
Connection:
77+
- keep-alive
78+
User-Agent:
79+
- mati-python/2.0.7.dev1
80+
method: GET
81+
uri: https://api.getmati.com/v2/verifications/686c77811ee936aece7016ac
82+
response:
83+
body:
84+
string: '{"expired": false, "identity": {"status": "verified"}, "flow": {"id":
85+
"some_flow", "name": "Default flow"}, "documents": [{"country": "MX", "region":
86+
"", "type": "national-id", "photos": ["https://media.getmati.com/media/xxx",
87+
"https://media.getmati.com/media/yyy"], "steps": [{"error": null, "status":
88+
200, "id": "template-matching"}, {"error": null, "status": 200, "id": "mexican-curp-validation",
89+
"data": {"curp": "CURP", "fullName": "LAST FIRST", "birthDate": "01/01/1980",
90+
"gender": "HOMBRE", "nationality": "MEXICO", "surname": "LAST", "secondSurname":
91+
"", "name": "FIRST"}}, {"error": null, "status": 200, "id": "document-reading",
92+
"data": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive":
93+
true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth":
94+
{"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate":
95+
{"value": "2030-12-31", "label": "Date of Expiration", "format": "date"},
96+
"curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia
97+
36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01",
98+
"label": "Emission Date", "format": "date"}}}, {"error": null, "status": 200,
99+
"id": "alteration-detection"}, {"error": null, "status": 200, "id": "watchlists"}],
100+
"fields": {"fullName": {"value": "FIRST LAST", "label": "Name", "sensitive":
101+
true}, "documentNumber": {"value": "111", "label": "Document Number"}, "dateOfBirth":
102+
{"value": "1980-01-01", "label": "Day of Birth", "format": "date"}, "expirationDate":
103+
{"value": "2030-12-31", "label": "Date of Expiration", "format": "date"},
104+
"curp": {"value": "CURP", "label": "CURP"}, "address": {"value": "Varsovia
105+
36, 06600 CDMX", "label": "Address"}, "emissionDate": {"value": "2010-01-01",
106+
"label": "Emission Date", "format": "date"}}}, {"country": "MX", "region":
107+
null, "type": "proof-of-residency", "steps": [{"status": 200, "id": "document-reading",
108+
"data": {"fullName": {"required": true, "label": "Name", "value": "FIRST NAME"},
109+
"address": {"label": "Address", "value": "Varsovia 36, 06600 CDMX"}, "emissionDate":
110+
{"format": "date", "label": "Emission Date", "value": "1880-01-01"}}, "error":
111+
null}, {"status": 200, "id": "watchlists", "error": null}], "fields": {"address":
112+
{"value": "Varsovia 36, 06600 CDMX"}, "emissionDate": {"value": "1880-01-01"},
113+
"fullName": {"value": "FIRST LASTNAME"}}, "photos": ["https://media.getmati.com/file?location=xyc"]}],
114+
"steps": [{"status": 200, "id": "liveness", "data": {"videoUrl": "https://media.getmati.com/file?location=abc",
115+
"spriteUrl": "https://media.getmati.com/file?location=def", "selfieUrl": "https://media.getmati.com/file?location=hij"},
116+
"error": null}], "hasProblem": false, "computed": {"age": {"data": 100}, "isDocumentExpired":
117+
{"data": {"national-id": false, "proof-of-residency": false}}}, "id": "5d9fb1f5bfbfac001a349bfb",
118+
"metadata": {"name": "First Last", "dob": "1980-01-01"}}'
119+
headers:
120+
Connection:
121+
- keep-alive
122+
Content-Length:
123+
- '7918'
124+
Content-Type:
125+
- application/json; charset=utf-8
126+
Date:
127+
- Tue, 08 Jul 2025 23:18:57 GMT
128+
cache-control:
129+
- no-store, no-cache, must-revalidate, proxy-revalidate
130+
content-security-policy:
131+
- 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self''
132+
https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src
133+
''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests'
134+
expect-ct:
135+
- max-age=0
136+
expires:
137+
- '0'
138+
pragma:
139+
- no-cache
140+
referrer-policy:
141+
- no-referrer
142+
strict-transport-security:
143+
- max-age=15552000; includeSubDomains; preload
144+
surrogate-control:
145+
- no-store
146+
x-content-type-options:
147+
- nosniff
148+
x-dns-prefetch-control:
149+
- 'off'
150+
x-download-options:
151+
- noopen
152+
x-envoy-upstream-service-time:
153+
- '258'
154+
x-frame-options:
155+
- DENY
156+
x-permitted-cross-domain-policies:
157+
- none
158+
x-request-id:
159+
- 98bc4ee3-a97d-442f-bfbc-5fdd49f302a7
160+
x-xss-protection:
161+
- '1'
162+
status:
163+
code: 200
164+
message: OK
165+
version: 1

0 commit comments

Comments
 (0)