179 lines
6.2 KiB
Python
179 lines
6.2 KiB
Python
"""
|
|
Common Utilities for the verify_student application.
|
|
"""
|
|
|
|
import datetime
|
|
import logging
|
|
|
|
from django.conf import settings
|
|
from django.utils.timezone import now
|
|
|
|
from lms.djangoapps.verify_student.tasks import send_request_to_ss_for_user
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def submit_request_to_ss(user_verification, copy_id_photo_from):
|
|
"""
|
|
Submit our verification attempt to Software Secure for validation.
|
|
|
|
Submits the task to software secure and If the task creation fails,
|
|
set the verification status to "must_retry".
|
|
"""
|
|
try:
|
|
send_request_to_ss_for_user.delay(
|
|
user_verification_id=user_verification.id, copy_id_photo_from=copy_id_photo_from
|
|
)
|
|
except Exception as error: # pylint: disable=broad-except
|
|
log.error(
|
|
"Software Secure submit request %r failed, result: %s", user_verification.user.username, str(error)
|
|
)
|
|
user_verification.mark_must_retry()
|
|
|
|
|
|
def is_verification_expiring_soon(expiration_datetime):
|
|
"""
|
|
Returns True if verification is expiring within EXPIRING_SOON_WINDOW.
|
|
"""
|
|
if expiration_datetime:
|
|
if (expiration_datetime - now()).days <= settings.VERIFY_STUDENT.get("EXPIRING_SOON_WINDOW"):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def earliest_allowed_verification_date():
|
|
"""
|
|
Returns the earliest allowed date given the settings
|
|
"""
|
|
days_good_for = settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]
|
|
return now() - datetime.timedelta(days=days_good_for)
|
|
|
|
|
|
def active_verifications(candidates, deadline):
|
|
"""
|
|
Based on the deadline, only return verification attempts
|
|
that are considered active (non-expired and wasn't created for future)
|
|
"""
|
|
relevant_attempts = []
|
|
if not candidates:
|
|
return relevant_attempts
|
|
|
|
# Look for a verification that was in effect at the deadline,
|
|
# preferring recent verifications.
|
|
# If no such verification is found, implicitly return empty array
|
|
for verification in candidates:
|
|
if verification.active_at_datetime(deadline):
|
|
relevant_attempts.append(verification)
|
|
|
|
return relevant_attempts
|
|
|
|
|
|
def verification_for_datetime(deadline, candidates):
|
|
"""Find a verification in a set that applied during a particular datetime.
|
|
|
|
A verification is considered "active" during a datetime if:
|
|
1) The verification was created before the datetime, and
|
|
2) The verification is set to expire after the datetime.
|
|
|
|
Note that verification status is *not* considered here,
|
|
just the start/expire dates.
|
|
|
|
If multiple verifications were active at the deadline,
|
|
returns the most recently created one.
|
|
|
|
Arguments:
|
|
deadline (datetime): The datetime at which the verification applied.
|
|
If `None`, then return the most recently created candidate.
|
|
candidates (list of `PhotoVerification`s): Potential verifications to search through.
|
|
|
|
Returns:
|
|
PhotoVerification: A photo verification that was active at the deadline.
|
|
If no verification was active, return None.
|
|
|
|
"""
|
|
if not candidates:
|
|
return None
|
|
|
|
if not deadline:
|
|
return candidates[0]
|
|
|
|
attempts = active_verifications(candidates, deadline)
|
|
if attempts:
|
|
return attempts[0]
|
|
else:
|
|
return None
|
|
|
|
|
|
def most_recent_verification(photo_id_verifications, sso_id_verifications, manual_id_verifications, most_recent_key):
|
|
"""
|
|
Return the most recent verification given querysets for photo, sso and manual verifications.
|
|
|
|
This function creates a map of the latest verification of all types and then returns the earliest
|
|
verification using the max of the map values.
|
|
|
|
Arguments:
|
|
photo_id_verifications: Queryset containing photo verifications
|
|
sso_id_verifications: Queryset containing sso verifications
|
|
manual_id_verifications: Queryset containing manual verifications
|
|
most_recent_key: Either 'updated_at' or 'created_at'
|
|
|
|
Returns:
|
|
The most recent verification.
|
|
"""
|
|
photo_id_verification = photo_id_verifications and photo_id_verifications.first()
|
|
sso_id_verification = sso_id_verifications and sso_id_verifications.first()
|
|
manual_id_verification = manual_id_verifications and manual_id_verifications.first()
|
|
|
|
verifications = [photo_id_verification, sso_id_verification, manual_id_verification]
|
|
|
|
verifications_map = {
|
|
verification: getattr(verification, most_recent_key)
|
|
for verification in verifications
|
|
if getattr(verification, most_recent_key, False)
|
|
}
|
|
|
|
return max(verifications_map, key=lambda k: verifications_map[k]) if verifications_map else None
|
|
|
|
|
|
def auto_verify_for_testing_enabled(override=None):
|
|
"""
|
|
If AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING is True, we want to skip posting
|
|
anything to Software Secure.
|
|
|
|
Bypass posting anything to Software Secure if auto verify feature for testing is enabled.
|
|
We actually don't even create the message because that would require encryption and message
|
|
signing that rely on settings.VERIFY_STUDENT values that aren't set in dev. So we just
|
|
pretend like we successfully posted.
|
|
"""
|
|
if override is not None:
|
|
return override
|
|
return settings.FEATURES.get('AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING')
|
|
|
|
|
|
def can_verify_now(verification_status, expiration_datetime):
|
|
"""
|
|
Returns whether one is eligible for verification now based on status and expiration.
|
|
|
|
Arguments:
|
|
verification_status (str)
|
|
expiration_datetime (datetime)
|
|
|
|
Returns: bool
|
|
"""
|
|
return (
|
|
# If the user has no initial verification or if the verification
|
|
# process is still ongoing 'pending' or expired then allow the user to
|
|
# submit the photo verification.
|
|
# A photo verification is marked as 'pending' if its status is either
|
|
# 'submitted' or 'must_retry'.
|
|
verification_status['status'] in {"none", "must_reverify", "expired", "pending"}
|
|
or (
|
|
# The user has an active verification, but the verification
|
|
# is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
|
|
# In this case user can resubmit photos for reverification.
|
|
expiration_datetime
|
|
and is_verification_expiring_soon(expiration_datetime)
|
|
)
|
|
)
|