2022-04-20 12:01:38 +00:00
|
|
|
# Other library imports
|
2022-04-20 12:18:56 +00:00
|
|
|
import hashlib
|
|
|
|
import hmac
|
|
|
|
import json
|
2022-07-15 10:09:54 +00:00
|
|
|
import time
|
2022-04-20 12:01:38 +00:00
|
|
|
|
2022-07-15 10:09:54 +00:00
|
|
|
import requests
|
|
|
|
import util
|
2023-02-09 07:20:00 +00:00
|
|
|
|
2022-04-20 12:01:38 +00:00
|
|
|
# Project imports
|
2022-04-20 12:18:56 +00:00
|
|
|
from settings import settings
|
2022-04-20 12:01:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Verify(util.Base):
|
|
|
|
"""
|
|
|
|
Class to handle user verification.
|
|
|
|
"""
|
2022-04-20 12:18:56 +00:00
|
|
|
|
2022-05-05 17:16:56 +00:00
|
|
|
def create_uid(self, platform, username):
|
|
|
|
return f"{platform}|{username}"
|
|
|
|
|
|
|
|
def get_uid(self, external_user_id):
|
|
|
|
"""
|
|
|
|
Get the platform and username from the external user ID.
|
|
|
|
"""
|
|
|
|
spl = external_user_id.split("|")
|
|
|
|
if not len(spl) == 2:
|
|
|
|
self.log.error(f"Split invalid, cannot get customer: {spl}")
|
|
|
|
return False
|
|
|
|
platform, username = spl
|
|
|
|
return (platform, username)
|
|
|
|
|
2022-04-20 18:08:10 +00:00
|
|
|
def verification_successful(self, external_user_id):
|
|
|
|
"""
|
|
|
|
Called when verification has been successfully passed.
|
|
|
|
"""
|
2022-05-05 17:16:56 +00:00
|
|
|
self.antifraud.user_verification_successful(external_user_id)
|
2022-04-20 18:08:10 +00:00
|
|
|
|
2022-07-15 10:09:54 +00:00
|
|
|
def update_verification_status(
|
|
|
|
self, external_user_id, review_status, review_answer=None
|
|
|
|
):
|
2022-04-20 18:08:10 +00:00
|
|
|
"""
|
|
|
|
Update the authentication status of a external user ID.
|
|
|
|
"""
|
|
|
|
if review_status == "completed" and review_answer == "GREEN":
|
|
|
|
self.verification_successful(external_user_id)
|
|
|
|
|
|
|
|
def verify_webhook_signature(self, content, payload_digest):
|
|
|
|
if type(content) == str:
|
|
|
|
content = content.encode("utf-8")
|
|
|
|
# hmac needs bytes
|
2022-07-15 10:09:54 +00:00
|
|
|
signature = hmac.new(
|
|
|
|
settings.Verify.WebHookSecret.encode("utf-8"),
|
|
|
|
content,
|
|
|
|
digestmod=hashlib.sha1,
|
|
|
|
).hexdigest()
|
2022-04-20 18:08:10 +00:00
|
|
|
|
|
|
|
return signature == payload_digest
|
|
|
|
|
|
|
|
def process_callback(self, content_json):
|
|
|
|
if "externalUserId" in content_json:
|
|
|
|
external_user_id = content_json["externalUserId"]
|
|
|
|
else:
|
|
|
|
self.log.warning("Useless callback received. No external user ID.")
|
|
|
|
return False
|
|
|
|
if "reviewStatus" in content_json:
|
|
|
|
review_status = content_json["reviewStatus"]
|
|
|
|
else:
|
|
|
|
self.log.warning("Useless callback received. No review status.")
|
|
|
|
return False
|
|
|
|
|
|
|
|
review_answer = None
|
|
|
|
if review_status == "completed":
|
|
|
|
if "reviewResult" in content_json:
|
|
|
|
if "reviewAnswer" in content_json["reviewResult"]:
|
|
|
|
review_answer = content_json["reviewResult"]["reviewAnswer"]
|
|
|
|
|
2022-07-15 10:09:54 +00:00
|
|
|
self.update_verification_status(
|
|
|
|
external_user_id, review_status, review_answer=review_answer
|
|
|
|
)
|
2022-04-20 18:08:10 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
def handle_callback(self, request):
|
|
|
|
"""
|
|
|
|
Handle a webhook callback.
|
|
|
|
"""
|
|
|
|
content = request.content.read()
|
|
|
|
payload_digest = request.getHeader("x-payload-digest")
|
|
|
|
if not self.verify_webhook_signature(content, payload_digest):
|
|
|
|
self.log.error("Webhook is not signed. Aborting.")
|
|
|
|
return False
|
|
|
|
content_json = json.loads(content)
|
|
|
|
rtrn = self.process_callback(content_json)
|
|
|
|
return rtrn
|
|
|
|
|
2022-04-23 10:53:19 +00:00
|
|
|
def get_external_user_id_details(self, external_user_id):
|
|
|
|
# /resources/applicants/-;externalUserId={externalUserId}/one
|
|
|
|
url = f"{settings.Verify.Base}/resources/applicants/-;externalUserId={external_user_id}/one"
|
|
|
|
resp = self.sign_request(requests.Request("GET", url))
|
|
|
|
s = requests.Session()
|
|
|
|
response = s.send(resp)
|
|
|
|
info = response.json()
|
|
|
|
if "info" in info:
|
|
|
|
if {"firstName", "lastName"}.issubset(set(info["info"].keys())):
|
|
|
|
first_name = info["info"]["firstName"]
|
|
|
|
last_name = info["info"]["lastName"]
|
|
|
|
if first_name.startswith("MR "):
|
|
|
|
first_name = first_name[3:]
|
|
|
|
return (first_name, last_name)
|
|
|
|
|
2022-04-20 12:55:45 +00:00
|
|
|
def create_applicant_and_get_link(self, external_user_id):
|
|
|
|
"""
|
|
|
|
Create the applicant and return the authentication link.
|
|
|
|
"""
|
|
|
|
# applicant_id = self.create_applicant(external_user_id)
|
|
|
|
auth_url = self.get_authentication_link(external_user_id)
|
|
|
|
return auth_url
|
|
|
|
|
2022-04-20 12:18:56 +00:00
|
|
|
def get_authentication_link(self, external_user_id):
|
|
|
|
"""
|
|
|
|
Get an external authentication link for a user.
|
|
|
|
"""
|
2022-04-20 12:24:00 +00:00
|
|
|
# /resources/sdkIntegrations/levels/{levelName}/websdkLink?ttlInSecs={lifetime}&externalUserId={externalUserId}&lang={locale}
|
|
|
|
url = (
|
|
|
|
f"{settings.Verify.Base}/resources/sdkIntegrations/levels/{settings.Verify.LevelName}"
|
|
|
|
f"/websdkLink?ttlInSecs=36000&externalUserId={external_user_id}"
|
|
|
|
)
|
|
|
|
resp = self.sign_request(requests.Request("POST", url))
|
|
|
|
s = requests.Session()
|
|
|
|
response = s.send(resp)
|
|
|
|
verification_url = response.json()["url"]
|
|
|
|
return verification_url
|
2022-04-20 12:18:56 +00:00
|
|
|
|
|
|
|
def get_applicant_status(self, applicant_id):
|
|
|
|
"""
|
|
|
|
Get the status of an applicant.
|
|
|
|
"""
|
|
|
|
# url = settings.Verify.Base + '/resources/applicants/' + applicant_id + '/requiredIdDocsStatus'
|
|
|
|
url = f"{settings.Verify.Base}/resources/applicants/'{applicant_id}/requiredIdDocsStatus"
|
|
|
|
resp = self.sign_request(requests.Request("GET", url))
|
|
|
|
s = requests.Session()
|
|
|
|
response = s.send(resp)
|
|
|
|
return response
|
|
|
|
|
|
|
|
def get_external_user_id_status(self, external_user_id):
|
|
|
|
"""
|
|
|
|
Get the status of an applicant by the external user ID.
|
|
|
|
"""
|
2022-07-15 10:09:54 +00:00
|
|
|
url = (
|
|
|
|
settings.Verify.Base
|
|
|
|
+ f"/resources/applicants/-;externalUserId={external_user_id}/one"
|
|
|
|
)
|
2022-04-20 12:18:56 +00:00
|
|
|
resp = self.sign_request(requests.Request("GET", url))
|
|
|
|
s = requests.Session()
|
|
|
|
response = s.send(resp)
|
2022-04-20 12:55:45 +00:00
|
|
|
response_json = response.json()
|
|
|
|
if "review" in response_json:
|
|
|
|
if "reviewResult" in response_json["review"]:
|
|
|
|
if "reviewAnswer" in response_json["review"]["reviewResult"]:
|
|
|
|
return response_json["review"]["reviewResult"]["reviewAnswer"]
|
|
|
|
return
|
2022-04-20 12:18:56 +00:00
|
|
|
|
2022-04-20 12:55:45 +00:00
|
|
|
def create_applicant(self, external_user_id):
|
2022-04-20 12:18:56 +00:00
|
|
|
"""
|
|
|
|
Create an applicant.
|
|
|
|
"""
|
|
|
|
body = {"externalUserId": external_user_id}
|
2022-04-20 12:55:45 +00:00
|
|
|
params = {"levelName": settings.Verify.LevelName}
|
2022-04-20 12:18:56 +00:00
|
|
|
headers = {"Content-Type": "application/json", "Content-Encoding": "utf-8"}
|
|
|
|
resp = self.sign_request(
|
|
|
|
requests.Request(
|
|
|
|
"POST",
|
2022-04-20 12:55:45 +00:00
|
|
|
f"{settings.Verify.Base}/resources/applicants?levelName={settings.Verify.LevelName}",
|
2022-04-20 12:18:56 +00:00
|
|
|
params=params,
|
|
|
|
data=json.dumps(body),
|
|
|
|
headers=headers,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
s = requests.Session()
|
|
|
|
response = s.send(resp)
|
|
|
|
applicant_id = response.json()["id"]
|
|
|
|
return applicant_id
|
|
|
|
|
|
|
|
def get_access_token(self, external_user_id, level_name):
|
|
|
|
"""
|
|
|
|
Get an access token for an external user ID.
|
|
|
|
"""
|
2022-07-15 10:09:54 +00:00
|
|
|
params = {
|
|
|
|
"userId": external_user_id,
|
|
|
|
"ttlInSecs": "600",
|
|
|
|
"levelName": level_name,
|
|
|
|
}
|
2022-04-20 12:18:56 +00:00
|
|
|
headers = {"Content-Type": "application/json", "Content-Encoding": "utf-8"}
|
|
|
|
resp = self.sign_request(
|
2022-07-15 10:09:54 +00:00
|
|
|
requests.Request(
|
|
|
|
"POST",
|
|
|
|
f"{settings.Verify.Base}/resources/accessTokens",
|
|
|
|
params=params,
|
|
|
|
headers=headers,
|
|
|
|
)
|
2022-04-20 12:18:56 +00:00
|
|
|
)
|
|
|
|
s = requests.Session()
|
|
|
|
response = s.send(resp)
|
|
|
|
token = response.json()["token"]
|
|
|
|
|
|
|
|
return token
|
|
|
|
|
|
|
|
def sign_request(self, request: requests.Request) -> requests.PreparedRequest:
|
|
|
|
"""
|
|
|
|
Sign a request.
|
|
|
|
"""
|
|
|
|
prepared_request = request.prepare()
|
|
|
|
now = int(time.time())
|
|
|
|
method = request.method.upper()
|
|
|
|
path_url = prepared_request.path_url # includes encoded query params
|
|
|
|
# could be None so we use an empty **byte** string here
|
|
|
|
body = b"" if prepared_request.body is None else prepared_request.body
|
|
|
|
if type(body) == str:
|
|
|
|
body = body.encode("utf-8")
|
2022-07-15 10:09:54 +00:00
|
|
|
data_to_sign = (
|
|
|
|
str(now).encode("utf-8")
|
|
|
|
+ method.encode("utf-8")
|
|
|
|
+ path_url.encode("utf-8")
|
|
|
|
+ body
|
|
|
|
)
|
2022-04-20 12:18:56 +00:00
|
|
|
# hmac needs bytes
|
2022-07-15 10:09:54 +00:00
|
|
|
signature = hmac.new(
|
|
|
|
settings.Verify.Key.encode("utf-8"), data_to_sign, digestmod=hashlib.sha256
|
|
|
|
)
|
2022-04-20 12:18:56 +00:00
|
|
|
prepared_request.headers["X-App-Token"] = settings.Verify.Token
|
|
|
|
prepared_request.headers["X-App-Access-Ts"] = str(now)
|
|
|
|
prepared_request.headers["X-App-Access-Sig"] = signature.hexdigest()
|
|
|
|
return prepared_request
|