From 90f5e42dbd5fe0cbd0385fd864afbd73858b0f77 Mon Sep 17 00:00:00 2001
From: Christie Rice <8483753+crice100@users.noreply.github.com>
Date: Tue, 16 Mar 2021 09:37:27 -0400
Subject: [PATCH] MICROBA-1055 Require a valid enrollment mode, even on the
 allowlist (#27012)

---
 common/djangoapps/course_modes/api.py           | 12 ++++++++++++
 .../decisions/001-allowlist-requirements.rst    | 13 ++++++++-----
 .../certificates/generation_handler.py          |  6 ++++++
 lms/djangoapps/certificates/queue.py            |  3 ++-
 .../tests/test_generation_handler.py            | 17 +++++++++++++++++
 lms/djangoapps/certificates/tests/test_queue.py |  3 ++-
 6 files changed, 47 insertions(+), 7 deletions(-)

diff --git a/common/djangoapps/course_modes/api.py b/common/djangoapps/course_modes/api.py
index 8da8167edef..d560779deb1 100644
--- a/common/djangoapps/course_modes/api.py
+++ b/common/djangoapps/course_modes/api.py
@@ -15,3 +15,15 @@ def get_paid_modes_for_course(course_run_id):
         A list of paid modes (strings) that the course has attached to it.
     """
     return _CourseMode.paid_modes_for_course(course_run_id)
+
+
+def is_eligible_for_certificate(mode_slug, status=None):
+    """
+    Returns whether or not the given mode_slug is eligible for a
+    certificate. Currently all modes other than 'audit' grant a
+    certificate. Note that audit enrollments which existed prior
+    to December 2015 *were* given certificates, so there will be
+    GeneratedCertificate records with mode='audit' which are
+    eligible.
+    """
+    return _CourseMode.is_eligible_for_certificate(mode_slug, status)
diff --git a/lms/djangoapps/certificates/docs/decisions/001-allowlist-requirements.rst b/lms/djangoapps/certificates/docs/decisions/001-allowlist-requirements.rst
index db286dc9857..97c80734846 100644
--- a/lms/djangoapps/certificates/docs/decisions/001-allowlist-requirements.rst
+++ b/lms/djangoapps/certificates/docs/decisions/001-allowlist-requirements.rst
@@ -22,12 +22,15 @@ won't necessarily have a course certificate available to them. To receive a
 downloadable allowlist course certificate, the following things must be true at
 the time the certificate is generated:
 
-* The user must be enrolled in the course
+* The user must have an enrollment in the course
+
+  * The enrollment mode must be eligible for a certificate
+  * The enrollment does not need to be active
+
 * The user must have an approved, unexpired, ID verification
-* The user must be on the allowlist for the course run (see the CertificateWhitelist model)
-* The user must not have an invalidated certificate for the course run (see the CertificateInvalidation model)
-* Certificate generation must be enabled for the course run
-* Automatic certificate generation must be enabled
+* The user must be on the allowlist for the course run (see the *CertificateWhitelist* model)
+* The user must not have an invalidated certificate for the course run (see the *CertificateInvalidation* model)
+* Automatic certificate generation must be globally enabled
 
 Note: the above requirements were written for the allowlist, which assumes the
 CourseWaffleFlag *certificates_revamp.use_allowlist* has been enabled for the
diff --git a/lms/djangoapps/certificates/generation_handler.py b/lms/djangoapps/certificates/generation_handler.py
index da047a59be5..62dd2f51c94 100644
--- a/lms/djangoapps/certificates/generation_handler.py
+++ b/lms/djangoapps/certificates/generation_handler.py
@@ -10,6 +10,7 @@ import logging
 
 from edx_toggles.toggles import LegacyWaffleFlagNamespace
 
+from common.djangoapps.course_modes import api as modes_api
 from common.djangoapps.student.models import CourseEnrollment
 from lms.djangoapps.certificates.models import (
     CertificateInvalidation,
@@ -164,6 +165,11 @@ def _can_generate_allowlist_certificate(user, course_key):
         log.info(f'{user.id} : {course_key} does not have an enrollment. Certificate cannot be generated.')
         return False
 
+    if not modes_api.is_eligible_for_certificate(enrollment_mode):
+        log.info(f'{user.id} : {course_key} has an enrollment mode of {enrollment_mode}, which is not eligible for an '
+                 f'allowlist certificate. Certificate cannot be generated.')
+        return False
+
     if not IDVerificationService.user_is_verified(user):
         log.info(f'{user.id} does not have a verified id. Certificate cannot be generated.')
         return False
diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py
index 31060569b94..d84ad71b8c9 100644
--- a/lms/djangoapps/certificates/queue.py
+++ b/lms/djangoapps/certificates/queue.py
@@ -15,6 +15,7 @@ from lxml.etree import ParserError, XMLSyntaxError
 from requests.auth import HTTPBasicAuth
 
 from capa.xqueue_interface import XQueueInterface, make_hashkey, make_xheader
+from common.djangoapps.course_modes import api as modes_api
 from common.djangoapps.course_modes.models import CourseMode
 from common.djangoapps.student.models import CourseEnrollment, UserProfile
 from lms.djangoapps.certificates.models import CertificateStatuses as status
@@ -289,7 +290,7 @@ class XQueueCertInterface:
         user_is_verified = IDVerificationService.user_is_verified(student)
         cert_mode = enrollment_mode
 
-        is_eligible_for_certificate = CourseMode.is_eligible_for_certificate(enrollment_mode, cert_status)
+        is_eligible_for_certificate = modes_api.is_eligible_for_certificate(enrollment_mode, cert_status)
         if is_whitelisted and not is_eligible_for_certificate:
             # check if audit certificates are enabled for audit mode
             is_eligible_for_certificate = enrollment_mode != CourseMode.AUDIT or \
diff --git a/lms/djangoapps/certificates/tests/test_generation_handler.py b/lms/djangoapps/certificates/tests/test_generation_handler.py
index 6c0f3da5ad6..fbdfb06bc25 100644
--- a/lms/djangoapps/certificates/tests/test_generation_handler.py
+++ b/lms/djangoapps/certificates/tests/test_generation_handler.py
@@ -195,6 +195,23 @@ class AllowlistTests(ModuleStoreTestCase):
         CertificateWhitelistFactory.create(course_id=key, user=u)
         assert not _can_generate_allowlist_certificate(u, key)
 
+    def test_can_generate_audit(self):
+        """
+        Test handling when user is enrolled in audit mode
+        """
+        u = UserFactory()
+        cr = CourseFactory()
+        key = cr.id  # pylint: disable=no-member
+        CourseEnrollmentFactory(
+            user=u,
+            course_id=key,
+            is_active=True,
+            mode="audit",
+        )
+        CertificateWhitelistFactory.create(course_id=key, user=u)
+
+        assert not _can_generate_allowlist_certificate(u, key)
+
     def test_can_generate_not_whitelisted(self):
         """
         Test handling when user is not whitelisted
diff --git a/lms/djangoapps/certificates/tests/test_queue.py b/lms/djangoapps/certificates/tests/test_queue.py
index 1e5ba4b77c7..82fd1064aab 100644
--- a/lms/djangoapps/certificates/tests/test_queue.py
+++ b/lms/djangoapps/certificates/tests/test_queue.py
@@ -21,6 +21,7 @@ from testfixtures import LogCapture
 # and verify that items are being correctly added to the queue
 # in our `XQueueCertInterface` implementation.
 from capa.xqueue_interface import XQueueInterface
+from common.djangoapps.course_modes import api as modes_api
 from common.djangoapps.course_modes.models import CourseMode
 from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
 from lms.djangoapps.certificates.models import (
@@ -91,7 +92,7 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
             id=self.course.id
         )
         mock_send = self.add_cert_to_queue(mode)
-        if CourseMode.is_eligible_for_certificate(mode):
+        if modes_api.is_eligible_for_certificate(mode):
             self.assert_certificate_generated(mock_send, mode, template_name)
         else:
             self.assert_ineligible_certificate_generated(mock_send, mode)
-- 
GitLab