diff --git a/lms/djangoapps/certificates/generation_handler.py b/lms/djangoapps/certificates/generation_handler.py index 1bb7f8d629..652865bb98 100644 --- a/lms/djangoapps/certificates/generation_handler.py +++ b/lms/djangoapps/certificates/generation_handler.py @@ -102,11 +102,8 @@ def can_generate_allowlist_certificate(user, course_key): )) return False - if not IDVerificationService.user_is_verified(user): - log.info( - '{user} does not have a verified id. Certificate cannot be generated.'.format( - user=user.id - )) + if not IDVerificationService.user_has_ever_been_verified(user): + log.info(f'{user.id} has not ever had a verified id. Certificate cannot be generated.') return False if not _is_on_certificate_allowlist(user, course_key): diff --git a/lms/djangoapps/certificates/tests/test_generation_handler.py b/lms/djangoapps/certificates/tests/test_generation_handler.py index a5b8cf06b1..14b6d11f6b 100644 --- a/lms/djangoapps/certificates/tests/test_generation_handler.py +++ b/lms/djangoapps/certificates/tests/test_generation_handler.py @@ -31,7 +31,7 @@ from openedx.core.djangoapps.certificates.config import waffle log = logging.getLogger(__name__) -ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified' +ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_has_ever_been_verified' AUTO_GENERATION_NAMESPACE = waffle.WAFFLE_NAMESPACE AUTO_GENERATION_NAME = waffle.AUTO_CERTIFICATE_GENERATION AUTO_GENERATION_SWITCH_NAME = '{}.{}'.format(AUTO_GENERATION_NAMESPACE, AUTO_GENERATION_NAME) diff --git a/lms/djangoapps/verify_student/services.py b/lms/djangoapps/verify_student/services.py index 3f97ce8963..a34a53d39e 100644 --- a/lms/djangoapps/verify_student/services.py +++ b/lms/djangoapps/verify_student/services.py @@ -18,7 +18,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ from common.djangoapps.student.models import User from .models import ManualVerification, SoftwareSecurePhotoVerification, SSOVerification -from .utils import earliest_allowed_verification_date, most_recent_verification, active_verifications # lint-amnesty, pylint: disable=unused-import +from .utils import most_recent_verification log = logging.getLogger(__name__) @@ -69,6 +69,31 @@ class IDVerificationService(object): return expiration_datetime >= now() return False + @classmethod + def user_has_ever_been_verified(cls, user): + """ + Return whether or not a user has ever satisfactorily proved their identity (has had an approved verification) + of any kind. + """ + if not user: + log.warning('No user provided. Verification attempts cannot be checked.') + return False + + if SoftwareSecurePhotoVerification.objects.filter(user=user, status='approved').exists(): + log.info(f'User {user.id} has an approved SoftwareSecurePhotoVerification') + return True + + if SSOVerification.objects.filter(user=user, status='approved').exists(): + log.info(f'User {user.id} has an approved SSOVerification') + return True + + if ManualVerification.objects.filter(user=user, status='approved').exists(): + log.info(f'User {user.id} has an approved ManualVerification') + return True + + log.info(f'User {user.id} has no approved verifications') + return False + @classmethod def verifications_for_user(cls, user): """ diff --git a/lms/djangoapps/verify_student/tests/test_services.py b/lms/djangoapps/verify_student/tests/test_services.py index f411d324dd..fb1058aa04 100644 --- a/lms/djangoapps/verify_student/tests/test_services.py +++ b/lms/djangoapps/verify_student/tests/test_services.py @@ -51,6 +51,46 @@ class TestIDVerificationService(ModuleStoreTestCase): attempt.save() assert IDVerificationService.user_is_verified(user), attempt.status + def test_user_has_ever_been_verified(self): + """ + Test to make sure we correctly answer whether a user has ever been verified. + """ + # Missing user + assert not IDVerificationService.user_has_ever_been_verified(None) + + # User without any attempts + photo_user = UserFactory.create() + assert not IDVerificationService.user_has_ever_been_verified(photo_user) + + # User without an approved attempt + attempt = SoftwareSecurePhotoVerification(user=photo_user, status='submitted') + attempt.save() + assert not IDVerificationService.user_has_ever_been_verified(photo_user) + + # User with a submitted, then an approved attempt + attempt = SoftwareSecurePhotoVerification(user=photo_user, status='approved') + attempt.save() + assert IDVerificationService.user_has_ever_been_verified(photo_user) + + # User with a manual approved attempt + manual_user = UserFactory.create() + attempt = ManualVerification(user=manual_user, status='approved') + attempt.save() + assert IDVerificationService.user_has_ever_been_verified(manual_user) + + # User with 2 manual approved attempts + attempt = ManualVerification(user=manual_user, status='approved') + attempt.save() + assert IDVerificationService.user_has_ever_been_verified(manual_user) + + # User with an SSO approved attempt, then a must_retry attempt + sso_user = UserFactory.create() + attempt = SSOVerification(user=sso_user, status='approved') + attempt.save() + attempt = SSOVerification(user=sso_user, status='must_retry') + attempt.save() + assert IDVerificationService.user_has_ever_been_verified(sso_user) + def test_user_has_valid_or_pending(self): """ Determine whether we have to prompt this user to verify, or if they've