146 lines
5.5 KiB
Python
146 lines
5.5 KiB
Python
"""
|
|
Django Celery tasks for service status app
|
|
"""
|
|
|
|
import logging
|
|
from smtplib import SMTPException
|
|
|
|
import requests
|
|
import simplejson
|
|
from celery import Task, shared_task
|
|
from celery.states import FAILURE
|
|
from django.conf import settings
|
|
from django.core.mail import EmailMessage
|
|
from edx_django_utils.monitoring import set_code_owner_attribute
|
|
|
|
from common.djangoapps.edxmako.shortcuts import render_to_string
|
|
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class BaseSoftwareSecureTask(Task): # lint-amnesty, pylint: disable=abstract-method
|
|
"""
|
|
Base task class for use with Software Secure request.
|
|
|
|
Permits updating information about user attempt in correspondence to submitting
|
|
request to software secure.
|
|
"""
|
|
abstract = True
|
|
|
|
def on_success(self, retval, task_id, args, kwargs):
|
|
"""
|
|
Update SoftwareSecurePhotoVerification object corresponding to this
|
|
task with info about success.
|
|
|
|
Updates user verification attempt to "submitted" if the response was ok otherwise
|
|
set it to "must_retry".
|
|
|
|
Assumes `retval` is a dict containing the task's result, with the following keys:
|
|
'response_ok': boolean, indicating if the response was ok
|
|
'response_text': string, indicating the response text in case of failure.
|
|
"""
|
|
from .models import SoftwareSecurePhotoVerification
|
|
|
|
user_verification = SoftwareSecurePhotoVerification.objects.get(id=kwargs['user_verification_id'])
|
|
if retval['response_ok']:
|
|
user_verification.mark_submit()
|
|
log.info(
|
|
'Sent request to Software Secure for user: %r and receipt ID %r.',
|
|
user_verification.user.username,
|
|
user_verification.receipt_id,
|
|
)
|
|
return user_verification
|
|
|
|
user_verification.mark_must_retry(retval['response_text'])
|
|
|
|
def after_return(self, status, retval, task_id, args, kwargs, einfo):
|
|
"""
|
|
If max retries have reached and task status is still failing, mark user submission
|
|
with "must_retry" so that it can be retried latter.
|
|
"""
|
|
if self.max_retries == self.request.retries and status == FAILURE:
|
|
from .models import SoftwareSecurePhotoVerification
|
|
|
|
user_verification_id = kwargs['user_verification_id']
|
|
user_verification = SoftwareSecurePhotoVerification.objects.get(id=user_verification_id)
|
|
user_verification.mark_must_retry()
|
|
log.error(
|
|
'Software Secure submission failed for user %r, setting status to must_retry',
|
|
user_verification.user.username,
|
|
exc_info=True
|
|
)
|
|
|
|
|
|
@shared_task
|
|
@set_code_owner_attribute
|
|
def send_verification_status_email(context):
|
|
"""
|
|
Spins a task to send verification status email to the learner
|
|
"""
|
|
subject = context.get('subject')
|
|
message = render_to_string(context.get('template'), context.get('email_vars'))
|
|
from_addr = configuration_helpers.get_value(
|
|
'email_from_address',
|
|
settings.DEFAULT_FROM_EMAIL
|
|
)
|
|
dest_addr = context.get('email')
|
|
|
|
try:
|
|
msg = EmailMessage(subject, message, from_addr, [dest_addr])
|
|
msg.content_subtype = 'html'
|
|
msg.send(fail_silently=False)
|
|
except SMTPException:
|
|
log.warning("Failure in sending verification status e-mail to %s", dest_addr)
|
|
|
|
|
|
@shared_task(
|
|
base=BaseSoftwareSecureTask,
|
|
bind=True,
|
|
default_retry_delay=settings.SOFTWARE_SECURE_REQUEST_RETRY_DELAY,
|
|
max_retries=settings.SOFTWARE_SECURE_RETRY_MAX_ATTEMPTS,
|
|
)
|
|
@set_code_owner_attribute
|
|
def send_request_to_ss_for_user(self, user_verification_id, copy_id_photo_from):
|
|
"""
|
|
Assembles a submission to Software Secure.
|
|
|
|
Keyword Arguments:
|
|
user_verification_id (int) SoftwareSecurePhotoVerification model object identifier.
|
|
copy_id_photo_from (SoftwareSecurePhotoVerification): If provided, re-send the ID photo
|
|
data from this attempt. This is used for re-verification, in which new face photos
|
|
are sent with previously-submitted ID photos.
|
|
Returns:
|
|
request.Response
|
|
"""
|
|
from .models import SoftwareSecurePhotoVerification
|
|
|
|
user_verification = SoftwareSecurePhotoVerification.objects.get(id=user_verification_id)
|
|
log.info('=>New Verification Task Received %r', user_verification.user.username)
|
|
try:
|
|
headers, body = user_verification.create_request(copy_id_photo_from)
|
|
# checkout PROD-1395 for detail why we are adding system certificate paths for verification.
|
|
response = requests.post(
|
|
settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["API_URL"],
|
|
headers=headers,
|
|
data=simplejson.dumps(body, indent=2, sort_keys=True, ensure_ascii=False).encode('utf-8'),
|
|
verify=settings.VERIFY_STUDENT["SOFTWARE_SECURE"]['CERT_VERIFICATION_PATH']
|
|
)
|
|
return {
|
|
'response_ok': getattr(response, 'ok', False),
|
|
'response_text': getattr(response, 'text', '')
|
|
}
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
log.error(
|
|
(
|
|
'Retrying sending request to Software Secure for user: %r, Receipt ID: %r '
|
|
'attempt#: %s of %s'
|
|
),
|
|
user_verification.user.username,
|
|
user_verification.receipt_id,
|
|
self.request.retries,
|
|
settings.SOFTWARE_SECURE_RETRY_MAX_ATTEMPTS,
|
|
)
|
|
log.error(str(exc))
|
|
self.retry()
|