Merge pull request #29922 from openedx/bseverino/integrity-signature-setting

[MST-1348] Replace integrity signature flag with django setting
This commit is contained in:
Bianca Severino
2022-02-15 11:18:36 -05:00
committed by GitHub
28 changed files with 165 additions and 238 deletions

View File

@@ -472,6 +472,18 @@ FEATURES = {
# .. toggle_target_removal_date: 2021-10-01
# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MICROBA-1405'
'ENABLE_V2_CERT_DISPLAY_SETTINGS': False,
# .. toggle_name: FEATURES['ENABLE_INTEGRITY_SIGNATURE']
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Whether to replace ID verification course/certificate requirement
# with an in-course Honor Code agreement
# (https://github.com/edx/edx-name-affirmation)
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2022-02-15
# .. toggle_target_removal_date: None
# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1348'
'ENABLE_INTEGRITY_SIGNATURE': False,
}
# .. toggle_name: ENABLE_COPPA_COMPLIANCE

View File

@@ -11,7 +11,6 @@ from slumber.exceptions import SlumberBaseException
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.helpers import VERIFY_STATUS_APPROVED, VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED # lint-amnesty, pylint: disable=line-too-long
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
DISPLAY_VERIFIED = "verified"
@@ -42,7 +41,7 @@ def enrollment_mode_display(mode, verification_status, course_id):
display_mode = _enrollment_mode_display(mode, verification_status, course_id)
if display_mode == DISPLAY_VERIFIED:
if is_integrity_signature_enabled(course_id) or verification_status == VERIFY_STATUS_APPROVED:
if settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') or verification_status == VERIFY_STATUS_APPROVED:
enrollment_title = _("You're enrolled as a verified student")
enrollment_value = _("Verified")
show_image = True
@@ -81,7 +80,7 @@ def _enrollment_mode_display(enrollment_mode, verification_status, course_id):
if enrollment_mode == CourseMode.VERIFIED:
if (
is_integrity_signature_enabled(course_id)
settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE')
or verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED]
):
display_mode = DISPLAY_VERIFIED

View File

@@ -11,10 +11,10 @@ from datetime import timedelta
from unittest.mock import patch
import ddt
from django.conf import settings
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
from django.utils.timezone import now
from edx_toggles.toggles.testutils import override_waffle_flag
from opaque_keys.edx.locator import CourseLocator
from common.djangoapps.course_modes.helpers import enrollment_mode_display
@@ -25,7 +25,6 @@ from common.djangoapps.course_modes.models import (
invalidate_course_mode_cache
)
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
@@ -367,25 +366,25 @@ class CourseModeModelTest(TestCase):
('no-id-professional', 'dummy', False)
)
@ddt.unpack
def test_enrollment_mode_display(self, mode, verification_status, enable_flag):
def test_enrollment_mode_display(self, mode, verification_status, enable_integrity_signature):
with override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=enable_flag):
with patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature):
if mode == "verified":
assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
self._enrollment_display_modes_dicts(mode, verification_status, enable_flag)
self._enrollment_display_modes_dicts(mode, verification_status, enable_integrity_signature)
assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
self._enrollment_display_modes_dicts(mode, verification_status, enable_flag)
self._enrollment_display_modes_dicts(mode, verification_status, enable_integrity_signature)
assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
self._enrollment_display_modes_dicts(mode, verification_status, enable_flag)
self._enrollment_display_modes_dicts(mode, verification_status, enable_integrity_signature)
elif mode == "honor":
assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
self._enrollment_display_modes_dicts(mode, mode, enable_flag)
self._enrollment_display_modes_dicts(mode, mode, enable_integrity_signature)
elif mode == "audit":
assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
self._enrollment_display_modes_dicts(mode, mode, enable_flag)
self._enrollment_display_modes_dicts(mode, mode, enable_integrity_signature)
elif mode == "professional":
assert enrollment_mode_display(mode, verification_status, self.course_key) ==\
self._enrollment_display_modes_dicts(mode, mode, enable_flag)
self._enrollment_display_modes_dicts(mode, mode, enable_integrity_signature)
@ddt.data(
(['honor', 'verified', 'credit'], ['honor', 'verified']),

View File

@@ -9,7 +9,6 @@ from django.conf import settings
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.ace_common.template_context import get_base_template_context
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangoapps.enrollments.api import is_enrollment_valid_for_proctoring
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
@@ -56,7 +55,7 @@ def generate_proctoring_requirements_email_context(user, course_id):
'course_name': course_module.display_name,
'proctoring_provider': capwords(course_module.proctoring_provider.replace('_', ' ')),
'proctoring_requirements_url': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('faq', ''),
'idv_required': not is_integrity_signature_enabled(course_id),
'idv_required': not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'),
'id_verification_url': IDVerificationService.get_verify_location(),
}

View File

@@ -50,7 +50,6 @@ from lms.djangoapps.instructor import access
from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.services import IDVerificationService
from lms.djangoapps.verify_student.utils import is_verification_expiring_soon, verification_for_datetime
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangoapps.content.block_structure.exceptions import UsageKeyNotInBlockStructure
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.theming.helpers import get_themes
@@ -117,14 +116,8 @@ def check_verify_status_by_course(user, course_enrollments):
"""
status_by_course = {}
# Before retriving verification data, if the integrity_signature feature is enabled for the course
# let's bypass all logic below. Filter down to those course with integrity_signature not enabled.
enabled_course_enrollments = []
for enrollment in course_enrollments:
if not is_integrity_signature_enabled(enrollment.course_id):
enabled_course_enrollments.append(enrollment)
if len(enabled_course_enrollments) == 0:
# If integrity signature is enabled, this is a no-op because IDV is not required
if settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
return status_by_course
# Retrieve all verifications for the user, sorted in descending
@@ -144,7 +137,7 @@ def check_verify_status_by_course(user, course_enrollments):
)
recent_verification_datetime = None
for enrollment in enabled_course_enrollments:
for enrollment in course_enrollments:
# If the user hasn't enrolled as verified, then the course
# won't display state related to its verification status.

View File

@@ -15,7 +15,6 @@ from django.test import TransactionTestCase, override_settings
from django.test.client import RequestFactory
from django.urls import reverse
from django.utils.html import escape
from edx_toggles.toggles.testutils import override_waffle_flag
from common.djangoapps.edxmako.shortcuts import marketing_link
from common.djangoapps.student.email_helpers import generate_proctoring_requirements_email_context
@@ -32,7 +31,6 @@ from common.djangoapps.third_party_auth.views import inactive_user_view
from common.djangoapps.util.testing import EventTestMixin
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.ace_common.tests.mixins import EmailTemplateTagMixin
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE, is_integrity_signature_enabled
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
from openedx.core.djangolib.testing.utils import CacheIsolationMixin, CacheIsolationTestCase
@@ -239,7 +237,7 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas
send_proctoring_requirements_email(context)
self._assert_email()
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_INTEGRITY_SIGNATURE': True})
def test_send_proctoring_requirements_email_honor(self):
self.course = CourseFactory(
display_name='honor code on course',
@@ -298,7 +296,7 @@ class ProctoringRequirementsEmailTests(EmailTemplateTagMixin, ModuleStoreTestCas
escape("Before taking a graded proctored exam, you must have approved ID verification photos."),
id_verification_url,
]
if not is_integrity_signature_enabled(self.course.id):
if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
fragments.extend(idv_fragments)
return (fragments, [])
return (fragments, idv_fragments)

View File

@@ -11,7 +11,6 @@ from django.conf import settings
from django.test import override_settings
from django.urls import reverse
from django.utils.timezone import now
from edx_toggles.toggles.testutils import override_waffle_flag
from pytz import UTC
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
@@ -28,7 +27,6 @@ from common.djangoapps.util.testing import UrlResetMixin
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
@@ -320,7 +318,7 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
self.assertContains(response2, attempt2.expiration_datetime.strftime("%m/%d/%Y"))
self.assertContains(response2, attempt2.expiration_datetime.strftime("%m/%d/%Y"), count=2)
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True)
@patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True})
@ddt.data(
None,
'past',
@@ -347,7 +345,7 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
self._assert_course_verification_status(None, "verified")
@ddt.data(True, False)
def test_integrity_disables_sidebar(self, integrity_flag):
def test_integrity_disables_sidebar(self, enable_integrity_signature):
self._setup_mode_and_enrollment(None, "verified")
#no sidebar when no IDV yet
@@ -360,11 +358,10 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
attempt.submit()
attempt.approve()
# sidebar only appears after IDV if integrity is not on
with patch('common.djangoapps.student.views.dashboard.is_integrity_signature_enabled',
return_value=integrity_flag):
# sidebar only appears after IDV if integrity signature setting is not on
with patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': enable_integrity_signature}):
response = self.client.get(self.dashboard_url)
if integrity_flag:
if enable_integrity_signature:
self.assertNotContains(response, "profile-sidebar")
else:
self.assertContains(response, "profile-sidebar")

View File

@@ -29,7 +29,6 @@ from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.experiments.utils import get_dashboard_course_info, get_experiment_user_metadata_context
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangoapps.catalog.utils import (
get_programs,
get_pseudo_session_for_entitlement,
@@ -749,16 +748,8 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem
else:
redirect_message = ''
all_integrity_enabled = True
if not course_enrollments:
all_integrity_enabled = is_integrity_signature_enabled(None)
for enrollment in course_enrollments:
if not is_integrity_signature_enabled(enrollment.course_id):
all_integrity_enabled = False
break
valid_verification_statuses = ['approved', 'must_reverify', 'pending', 'expired']
display_sidebar_on_dashboard = not all_integrity_enabled and \
display_sidebar_on_dashboard = not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') and \
verification_status['status'] in valid_verification_statuses and \
verification_status['should_display']

View File

@@ -35,7 +35,6 @@ from xmodule.x_module import (
)
from common.djangoapps.xblock_django.constants import ATTR_KEY_USER_ID, ATTR_KEY_USER_IS_STAFF
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from .exceptions import NotFoundError
from .fields import Date
@@ -950,7 +949,7 @@ class SequenceBlock(
'allow_proctoring_opt_out': self.allow_proctoring_opt_out,
'due_date': self.due,
'grace_period': self.graceperiod, # lint-amnesty, pylint: disable=no-member
'is_integrity_signature_enabled': is_integrity_signature_enabled(course_id),
'is_integrity_signature_enabled': settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'),
}
# inject the user's credit requirements and fulfillments

View File

@@ -7,6 +7,7 @@ cannot be generated, a message is logged and no further action is taken.
"""
import logging
from django.conf import settings
from common.djangoapps.course_modes import api as modes_api
from common.djangoapps.course_modes.models import CourseMode
@@ -22,7 +23,6 @@ from lms.djangoapps.certificates.utils import has_html_certificates_enabled
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.instructor.access import is_beta_tester
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none
log = logging.getLogger(__name__)
@@ -179,7 +179,7 @@ def _can_generate_certificate_common(user, course_key, enrollment_mode):
# If the IDV check fails we then check if the course-run requires ID verification. Honor and Professional-No-ID
# modes do not require IDV for certificate generation.
if _required_verification_missing(course_key, user):
if _required_verification_missing(user):
if enrollment_mode not in CourseMode.NON_VERIFIED_MODES:
log.info(f'{user.id} does not have a verified id. Certificate cannot be generated for {course_key}.')
return False
@@ -232,7 +232,7 @@ def _set_regular_cert_status(user, course_key, enrollment_mode, course_grade):
if status is not None:
return status
if not _required_verification_missing(course_key, user) \
if not _required_verification_missing(user) \
and not _is_passing_grade(course_grade) \
and cert is not None:
if cert.status != CertificateStatuses.notpassing:
@@ -255,8 +255,8 @@ def _get_cert_status_common(user, course_key, enrollment_mode, course_grade, cer
cert.invalidate(mode=enrollment_mode, source='certificate_generation')
return CertificateStatuses.unavailable
if _required_verification_missing(course_key, user) and _has_passing_grade_or_is_allowlisted(user, course_key,
course_grade):
if _required_verification_missing(user) and _has_passing_grade_or_is_allowlisted(user, course_key,
course_grade):
if cert is None:
_generate_certificate_task(user=user, course_key=course_key, enrollment_mode=enrollment_mode,
course_grade=course_grade, status=CertificateStatuses.unverified,
@@ -416,8 +416,8 @@ def _is_mode_now_eligible(enrollment_mode, cert):
return False
def _required_verification_missing(course_key, user):
def _required_verification_missing(user):
"""
Return true if IDV is required for this course and the user does not have it
"""
return not is_integrity_signature_enabled(course_key) and not IDVerificationService.user_is_verified(user)
return not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') and not IDVerificationService.user_is_verified(user)

View File

@@ -13,7 +13,6 @@ from opaque_keys.edx.keys import CourseKey
from lms.djangoapps.certificates.data import CertificateStatuses
from lms.djangoapps.certificates.generation_handler import generate_certificate_task
from lms.djangoapps.certificates.models import GeneratedCertificate
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
log = logging.getLogger(__name__)
User = get_user_model()
@@ -106,10 +105,6 @@ def _handle_course(course_key, batch_size, sleep_seconds, count):
Returns how many certs were originally unverified and regenerated.
"""
if not is_integrity_signature_enabled(course_key):
log.warning(f'Skipping {course_key} which does not have honor code enabled')
return 0
certs = GeneratedCertificate.objects.filter(
course_id=course_key,
status=CertificateStatuses.unverified,
@@ -117,7 +112,7 @@ def _handle_course(course_key, batch_size, sleep_seconds, count):
log.info(f'Regenerating {len(certs)} unverified certificates for {course_key}')
return _regenerate_certs(certs, batch_size, sleep_seconds, count, False)
return _regenerate_certs(certs, batch_size, sleep_seconds, count)
def _handle_all_courses(batch_size, sleep_seconds, excluded_keys):
@@ -130,24 +125,18 @@ def _handle_all_courses(batch_size, sleep_seconds, excluded_keys):
.exclude(course_id__in=excluded_keys)
log.info(f'Regenerating {len(certs)} unverified certificates in all courses except for excluded keys')
return _regenerate_certs(certs, batch_size, sleep_seconds, 0, True)
return _regenerate_certs(certs, batch_size, sleep_seconds, 0)
def _regenerate_certs(certs, batch_size, sleep_seconds, count, check_integrity_signature_flag):
def _regenerate_certs(certs, batch_size, sleep_seconds, count):
"""
Triggers generate certificate task for a given set of certificates
"""
for cert in certs:
# not ideal to replicate this check, but we have to in the case that we are regenerating certs for all courses.
if check_integrity_signature_flag and not is_integrity_signature_enabled(cert.course_id):
log.warning(
f'Skipping regenerating cert_id={cert.id} because {cert.course_id} does not have honor code enabled'
)
else:
user = User.objects.get(id=cert.user_id)
generate_certificate_task(user, cert.course_id, generation_mode='batch', delay_seconds=0)
count += 1
if count % batch_size == 0:
log.info(f'Regenerated {count} unverified certificates. Sleeping for {sleep_seconds} seconds.')
time.sleep(sleep_seconds)
user = User.objects.get(id=cert.user_id)
generate_certificate_task(user, cert.course_id, generation_mode='batch', delay_seconds=0)
count += 1
if count % batch_size == 0:
log.info(f'Regenerated {count} unverified certificates. Sleeping for {sleep_seconds} seconds.')
time.sleep(sleep_seconds)
return count

View File

@@ -5,6 +5,7 @@ Tests for the cert_generation command
from unittest import mock
import pytest
from django.conf import settings
from django.core.management import CommandError, call_command
from common.djangoapps.course_modes.models import CourseMode
@@ -15,19 +16,15 @@ from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFact
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
COMMAND_INTEGRITY_ENABLED = \
'lms.djangoapps.certificates.management.commands.regenerate_noidv_cert.is_integrity_signature_enabled'
INTEGRITY_ENABLED_METHOD = 'lms.djangoapps.certificates.generation_handler.is_integrity_signature_enabled'
ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
PASSING_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_passing_grade'
WEB_CERTS_METHOD = 'lms.djangoapps.certificates.generation_handler.has_html_certificates_enabled'
# base setup is unverified users, honor code (integrity) turned on wherever imports it
# base setup is unverified users, integrity signature turned on,
# and normal passing grade certificates for convenience
@mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=True)
@mock.patch(ID_VERIFIED_METHOD, mock.Mock(return_value=False))
@mock.patch(INTEGRITY_ENABLED_METHOD, mock.Mock(return_value=True))
@mock.patch(COMMAND_INTEGRITY_ENABLED, mock.Mock(return_value=True))
@mock.patch(PASSING_GRADE_METHOD, mock.Mock(return_value=True))
@mock.patch(WEB_CERTS_METHOD, mock.Mock(return_value=True))
class RegenerateNoIDVCertTests(ModuleStoreTestCase):
@@ -104,31 +101,6 @@ class RegenerateNoIDVCertTests(ModuleStoreTestCase):
regenerated = call_command("regenerate_noidv_cert", "-c", course_run_key)
self.assertEqual('0', regenerated)
def test_regeneration_honor_off(self):
"""
If a course does not have the honor code enabled, no point regenerating
"""
course_run = CourseFactory()
course_run_key = course_run.id
user = UserFactory()
CourseEnrollmentFactory(
user=user,
course_id=course_run_key,
is_active=True,
mode=CourseMode.VERIFIED,
)
GeneratedCertificateFactory(
user=user,
course_id=course_run_key,
mode=GeneratedCertificate.MODES.verified,
status=CertificateStatuses.unverified
)
with mock.patch(COMMAND_INTEGRITY_ENABLED, mock.Mock(return_value=False)):
regenerated = call_command("regenerate_noidv_cert", "-c", course_run_key)
self.assertEqual('0', regenerated)
def _multisetup(self):
"""
setup certs across two course runs

View File

@@ -38,13 +38,11 @@ BETA_TESTER_METHOD = 'lms.djangoapps.certificates.generation_handler.is_beta_tes
COURSE_OVERVIEW_METHOD = 'lms.djangoapps.certificates.generation_handler.get_course_overview_or_none'
CCX_COURSE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_ccx_course'
GET_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._get_course_grade'
INTEGRITY_ENABLED_METHOD = 'lms.djangoapps.certificates.generation_handler.is_integrity_signature_enabled'
ID_VERIFIED_METHOD = 'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
PASSING_GRADE_METHOD = 'lms.djangoapps.certificates.generation_handler._is_passing_grade'
WEB_CERTS_METHOD = 'lms.djangoapps.certificates.generation_handler.has_html_certificates_enabled'
@mock.patch(INTEGRITY_ENABLED_METHOD, mock.Mock(return_value=False))
@mock.patch(ID_VERIFIED_METHOD, mock.Mock(return_value=True))
@mock.patch(WEB_CERTS_METHOD, mock.Mock(return_value=True))
@ddt.ddt
@@ -204,15 +202,15 @@ class AllowlistTests(ModuleStoreTestCase):
assert generate_certificate_task(self.user, self.course_run_key)
@ddt.data(False, True)
def test_can_generate_not_verified(self, idv_retired):
def test_can_generate_not_verified(self, enable_integrity_signature):
"""
Test handling when the user's id is not verified
"""
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired):
self.assertEqual(idv_retired,
mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature):
self.assertEqual(enable_integrity_signature,
_can_generate_allowlist_certificate(self.user, self.course_run_key, self.enrollment_mode))
self.assertIsNot(idv_retired,
self.assertIsNot(enable_integrity_signature,
_set_allowlist_cert_status(
self.user, self.course_run_key,
self.enrollment_mode, self.grade) == CertificateStatuses.unverified)
@@ -358,7 +356,7 @@ class AllowlistTests(ModuleStoreTestCase):
assert not _can_generate_allowlist_certificate(self.user, course_run_key, enrollment_mode)
@mock.patch(INTEGRITY_ENABLED_METHOD, mock.Mock(return_value=False))
@mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=False)
@mock.patch(ID_VERIFIED_METHOD, mock.Mock(return_value=True))
@mock.patch(CCX_COURSE_METHOD, mock.Mock(return_value=False))
@mock.patch(PASSING_GRADE_METHOD, mock.Mock(return_value=True))
@@ -516,7 +514,7 @@ class CertificateTests(ModuleStoreTestCase):
assert _can_generate_certificate_for_status(None, None, None)
@ddt.data(False, True)
def test_can_generate_not_verified_cert(self, idv_retired):
def test_can_generate_not_verified_cert(self, enable_integrity_signature):
"""
Test handling when the user's id is not verified and they have a cert
"""
@@ -535,14 +533,16 @@ class CertificateTests(ModuleStoreTestCase):
)
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired):
self.assertEqual(idv_retired, _can_generate_regular_certificate(u, self.course_run_key,
self.enrollment_mode, self.grade))
self.assertIsNot(idv_retired, _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode,
self.grade) == CertificateStatuses.unverified)
mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature):
self.assertEqual(
enable_integrity_signature,
_can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade)
)
regular_cert_status = _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade)
self.assertIsNot(enable_integrity_signature, regular_cert_status == CertificateStatuses.unverified)
@ddt.data(False, True)
def test_can_generate_not_verified_no_cert(self, idv_retired):
def test_can_generate_not_verified_no_cert(self, enable_integrity_signature):
"""
Test handling when the user's id is not verified and they don't have a cert
"""
@@ -555,14 +555,16 @@ class CertificateTests(ModuleStoreTestCase):
)
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired):
self.assertEqual(idv_retired, _can_generate_regular_certificate(u, self.course_run_key,
self.enrollment_mode, self.grade))
self.assertIsNot(idv_retired, _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode,
self.grade) == CertificateStatuses.unverified)
mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature):
self.assertEqual(
enable_integrity_signature,
_can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade)
)
regular_cert_status = _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade)
self.assertIsNot(enable_integrity_signature, regular_cert_status == CertificateStatuses.unverified)
@ddt.data(False, True)
def test_can_generate_not_verified_not_passing(self, idv_retired):
def test_can_generate_not_verified_not_passing(self, enable_integrity_signature):
"""
Test handling when the user's id is not verified and the user is not passing
"""
@@ -581,17 +583,17 @@ class CertificateTests(ModuleStoreTestCase):
)
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired), \
mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature), \
mock.patch(PASSING_GRADE_METHOD, return_value=False):
assert not _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade)
if idv_retired:
if enable_integrity_signature:
assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) \
== CertificateStatuses.notpassing
else:
assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode, self.grade) is None
@ddt.data(False, True)
def test_can_generate_not_verified_not_passing_allowlist(self, idv_retired):
def test_can_generate_not_verified_not_passing_allowlist(self, enable_integrity_signature):
"""
Test handling when the user's id is not verified and the user is not passing but is on the allowlist
"""
@@ -611,10 +613,10 @@ class CertificateTests(ModuleStoreTestCase):
CertificateAllowlistFactory(course_id=self.course_run_key, user=u)
with mock.patch(ID_VERIFIED_METHOD, return_value=False), \
mock.patch(INTEGRITY_ENABLED_METHOD, return_value=idv_retired), \
mock.patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature), \
mock.patch(PASSING_GRADE_METHOD, return_value=False):
assert not _can_generate_regular_certificate(u, self.course_run_key, self.enrollment_mode, self.grade)
if idv_retired:
if enable_integrity_signature:
assert _set_regular_cert_status(u, self.course_run_key, self.enrollment_mode,
self.grade) == CertificateStatuses.notpassing
else:

View File

@@ -1643,34 +1643,33 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase)
self.assertNotContains(response, verified_name)
@override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED)
@patch('lms.djangoapps.certificates.views.webview.is_integrity_signature_enabled')
@ddt.data(
True,
False
)
def test_verified_certificate_description(self, integrity_signature_enabled, mock_integrity_signature):
def test_verified_certificate_description(self, enable_integrity_signature):
"""
Test that for a verified cert, the correct language is used when the integrity signature feature is enabled.
"""
mock_integrity_signature.return_value = integrity_signature_enabled
self._add_course_certificates(count=1, signatory_count=2, is_active=True)
self._create_custom_template_with_verified_description()
self.cert.mode = 'verified'
self.cert.save()
test_url = get_certificate_url(
user_id=self.user.id,
course_id=str(self.course.id),
uuid=self.cert.verify_uuid
)
with patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature):
self._add_course_certificates(count=1, signatory_count=2, is_active=True)
self._create_custom_template_with_verified_description()
self.cert.mode = 'verified'
self.cert.save()
test_url = get_certificate_url(
user_id=self.user.id,
course_id=str(self.course.id),
uuid=self.cert.verify_uuid
)
response = self.client.get(test_url)
assert response.status_code == 200
if not integrity_signature_enabled:
self.assertContains(response, 'identity of the learner has been checked and is valid')
self.assertNotContains(response, 'Integrity signature enabled')
else:
self.assertNotContains(response, 'identity of the learner has been checked and is valid')
self.assertContains(response, 'Integrity signature enabled')
response = self.client.get(test_url)
assert response.status_code == 200
if not enable_integrity_signature:
self.assertContains(response, 'identity of the learner has been checked and is valid')
self.assertNotContains(response, 'Integrity signature enabled')
else:
self.assertNotContains(response, 'identity of the learner has been checked and is valid')
self.assertContains(response, 'Integrity signature enabled')
class CertificateEventTests(CommonCertificatesTestCase, EventTrackingTestCase):

View File

@@ -49,7 +49,6 @@ from lms.djangoapps.certificates.utils import (
get_certificate_url,
get_preferred_certificate_name
)
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangoapps.catalog.api import get_course_run_details
from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_none
from openedx.core.djangoapps.lang_pref.api import get_closest_released_language
@@ -85,7 +84,7 @@ def get_certificate_description(mode, certificate_type, platform_name, course_ke
"{platform_name} and has completed all of the required tasks for this course "
"under its guidelines. ").format(cert_type=certificate_type,
platform_name=platform_name)
if not is_integrity_signature_enabled(course_key):
if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
certificate_type_description += _("A {cert_type} certificate also indicates that the "
"identity of the learner has been checked and "
"is valid.").format(cert_type=certificate_type)
@@ -253,7 +252,7 @@ def _update_course_context(request, context, course, platform_name):
context['accomplishment_copy_course_name'] = accomplishment_copy_course_name
course_number = course.display_coursenumber if course.display_coursenumber else course.number
context['course_number'] = course_number
context['is_integrity_signature_enabled_for_course'] = is_integrity_signature_enabled(course.location.course_key)
context['is_integrity_signature_enabled_for_course'] = settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE')
if context['organization_long_name']:
# Translators: This text represents the description of course
context['accomplishment_copy_course_description'] = _('a course of study offered by {partner_short_name}, '

View File

@@ -24,7 +24,6 @@ from lms.djangoapps.certificates.api import get_active_web_certificate, can_show
from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, can_show_verified_upgrade
from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_duration_limits.access import get_user_course_expiration_date
from openedx.features.course_experience import RELATIVE_DATES_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages
@@ -721,7 +720,7 @@ class VerificationDeadlineDate(DateSummary):
is_active and
mode == 'verified' and
self.verification_status in ('expired', 'none', 'must_reverify') and
not is_integrity_signature_enabled(self.course_id)
not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE')
)
@lazy

View File

@@ -3,10 +3,12 @@
from datetime import datetime, timedelta
from unittest.mock import patch
import crum
import ddt
import waffle # lint-amnesty, pylint: disable=invalid-django-waffle-import
from django.conf import settings
from django.contrib.messages.middleware import MessageMiddleware
from django.test import RequestFactory
from django.urls import reverse
@@ -40,7 +42,6 @@ from lms.djangoapps.courseware.models import (
from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.services import IDVerificationService
from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
@@ -656,7 +657,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
block = VerificationDeadlineDate(course, user)
assert not block.is_allowed
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True)
@patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True})
def test_verification_deadline_with_integrity_signature(self):
course = create_course_run(days_till_start=-1)
user = create_user()

View File

@@ -99,7 +99,6 @@ from lms.djangoapps.grades.config.waffle import waffle_switch as grades_waffle_s
from lms.djangoapps.instructor.access import allow_access
from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory
from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory, ProgramFactory
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
@@ -1930,7 +1929,7 @@ class ProgressPageTests(ProgressPageBaseTests):
assert response.title == 'Your certificate will be available soon!'
@ddt.data(True, False)
def test_no_certs_generated_and_not_verified(self, waffle_override):
def test_no_certs_generated_and_not_verified(self, enable_integrity_signature):
"""
Verify if the learner is not ID Verified, and the certs are not yet generated,
but the learner is eligible, the get_cert_data would return cert status Unverified
@@ -1939,14 +1938,14 @@ class ProgressPageTests(ProgressPageBaseTests):
CertificateGenerationCourseSetting(
course_key=self.course.id, self_generation_enabled=True
).save()
with override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=waffle_override):
with patch.dict(settings.FEATURES, ENABLE_INTEGRITY_SIGNATURE=enable_integrity_signature):
with patch(
'lms.djangoapps.certificates.api.certificate_downloadable_status',
return_value=self.mock_certificate_downloadable_status()
):
response = views.get_cert_data(self.user, self.course, CourseMode.VERIFIED, MagicMock(passed=True))
if waffle_override:
if enable_integrity_signature:
assert response.cert_status == 'requesting'
assert response.title == 'Congratulations, you qualified for a certificate!'
else:

View File

@@ -106,7 +106,6 @@ from lms.djangoapps.instructor.enrollment import uses_shib
from lms.djangoapps.instructor.views.api import require_global_staff
from lms.djangoapps.survey import views as survey_views
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangoapps.catalog.utils import get_programs, get_programs_with_type
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.credit.api import (
@@ -1254,8 +1253,8 @@ def _downloadable_certificate_message(course, cert_downloadable_status): # lint
return _downloadable_cert_data(download_url=cert_downloadable_status['download_url'])
def _missing_required_verification(student, enrollment_mode, course_key):
return not is_integrity_signature_enabled(course_key) and (
def _missing_required_verification(student, enrollment_mode):
return not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE') and (
enrollment_mode in CourseMode.VERIFIED_MODES and not IDVerificationService.user_is_verified(student)
)
@@ -1278,7 +1277,7 @@ def _certificate_message(student, course, enrollment_mode): # lint-amnesty, pyl
if cert_downloadable_status['is_downloadable']:
return _downloadable_certificate_message(course, cert_downloadable_status)
if _missing_required_verification(student, enrollment_mode, course.id):
if _missing_required_verification(student, enrollment_mode):
return UNVERIFIED_CERT_DATA
return REQUESTING_CERT_DATA

View File

@@ -2812,28 +2812,28 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
True,
False
)
@patch('lms.djangoapps.instructor.views.api.is_integrity_signature_enabled')
@valid_problem_location
def test_idv_retirement_student_features_report(self, toggle_value, mock_toggle):
mock_toggle.return_value = toggle_value
kwargs = {'course_id': str(self.course.id)}
kwargs.update({'csv': '/csv'})
url = reverse('get_students_features', kwargs=kwargs)
success_status = 'The enrolled learner profile report is being created.'
with patch('lms.djangoapps.instructor_task.api.submit_calculate_students_features_csv') as mock_task_endpoint:
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
response = self.client.post(url, {})
self.assertContains(response, success_status)
def test_idv_retirement_student_features_report(self, enable_integrity_signature):
with patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': enable_integrity_signature}):
kwargs = {'course_id': str(self.course.id)}
kwargs.update({'csv': '/csv'})
url = reverse('get_students_features', kwargs=kwargs)
success_status = 'The enrolled learner profile report is being created.'
with patch('lms.djangoapps.instructor_task.api.submit_calculate_students_features_csv') \
as mock_task_endpoint:
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
response = self.client.post(url, {})
self.assertContains(response, success_status)
# assert that if the integrity signature is enabled, the verification
# status is not included as a query feature
args = mock_task_endpoint.call_args.args
self.assertEqual(len(args), 3)
query_features = args[2]
if not toggle_value:
self.assertIn('verification_status', query_features)
else:
self.assertNotIn('verification_status', query_features)
# assert that if the integrity signature is enabled, the verification
# status is not included as a query feature
args = mock_task_endpoint.call_args.args
self.assertEqual(len(args), 3)
query_features = args[2]
if not enable_integrity_signature:
self.assertIn('verification_status', query_features)
else:
self.assertNotIn('verification_status', query_features)
def test_get_ora2_responses_success(self):
url = reverse('export_ora2_data', kwargs={'course_id': str(self.course.id)})

View File

@@ -102,7 +102,6 @@ from lms.djangoapps.instructor_analytics import basic as instructor_analytics_ba
from lms.djangoapps.instructor_task import api as task_api
from lms.djangoapps.instructor_task.api_helper import AlreadyRunningError, QueueConnectionError
from lms.djangoapps.instructor_task.models import ReportStore
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, is_course_cohorted
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
@@ -1461,7 +1460,7 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red
query_features.append('team')
query_features_names['team'] = _('Team')
if is_integrity_signature_enabled(course_key):
if settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
if 'verification_status' in query_features:
query_features.remove('verification_status')
query_features_names.pop('verification_status')

View File

@@ -2080,7 +2080,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
'failed': 0,
'skipped': 2
}
with self.assertNumQueries(83):
with self.assertNumQueries(76):
self.assertCertificatesGenerated(task_input, expected_results)
@ddt.data(

View File

@@ -948,6 +948,18 @@ FEATURES = {
# .. toggle_target_removal_date: 2021-10-01
# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MICROBA-1405'
'ENABLE_V2_CERT_DISPLAY_SETTINGS': False,
# .. toggle_name: FEATURES['ENABLE_INTEGRITY_SIGNATURE']
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Whether to replace ID verification course/certificate requirement
# with an in-course Honor Code agreement
# (https://github.com/edx/edx-name-affirmation)
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2022-02-15
# .. toggle_target_removal_date: None
# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1348'
'ENABLE_INTEGRITY_SIGNATURE': False,
}
# Specifies extra XBlock fields that should available when requested via the Course Blocks API

View File

@@ -3,11 +3,12 @@ Tests for agreements views
"""
from datetime import datetime, timedelta
from unittest.mock import patch
from django.conf import settings
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status
from edx_toggles.toggles.testutils import override_waffle_flag
from freezegun import freeze_time
from common.djangoapps.student.tests.factories import UserFactory, AdminFactory
@@ -16,14 +17,13 @@ from openedx.core.djangoapps.agreements.api import (
create_integrity_signature,
get_integrity_signatures_for_course,
)
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
from openedx.core.djangolib.testing.utils import skip_unless_lms
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
@skip_unless_lms
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True)
@patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True})
class IntegritySignatureViewTests(APITestCase, ModuleStoreTestCase):
"""
Tests for the Integrity Signature View
@@ -157,7 +157,7 @@ class IntegritySignatureViewTests(APITestCase, ModuleStoreTestCase):
)
self._assert_response(response, status.HTTP_200_OK, self.user, self.course_id)
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=False)
@patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': False})
def test_404_for_no_waffle_flag(self):
self._create_signature(self.user.username, self.course_id)
response = self.client.get(
@@ -209,7 +209,7 @@ class IntegritySignatureViewTests(APITestCase, ModuleStoreTestCase):
self.assertEqual(len(signatures), 1)
self.assertEqual(signatures[0].user.username, self.USERNAME)
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=False)
@patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': False})
def test_post_integrity_signature_no_waffle_flag(self):
response = self.client.post(
reverse(

View File

@@ -1,28 +0,0 @@
"""
Toggles for the Agreements app
"""
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
# .. toggle_name: agreements.enable_integrity_signature
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: Supports rollout of the integrity signature feature
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2021-05-07
# .. toggle_target_removal_date: None
# .. toggle_warnings: None
# .. toggle_tickets: MST-786
ENABLE_INTEGRITY_SIGNATURE = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation
'agreements', 'enable_integrity_signature', __name__,
)
def is_integrity_signature_enabled(course_key):
if isinstance(course_key, str):
course_key = CourseKey.from_string(course_key)
return ENABLE_INTEGRITY_SIGNATURE.is_enabled(course_key)

View File

@@ -2,6 +2,7 @@
Views served by the Agreements app
"""
from django.conf import settings
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from rest_framework import status
from rest_framework.views import APIView
@@ -17,7 +18,6 @@ from openedx.core.djangoapps.agreements.api import (
get_integrity_signature,
)
from openedx.core.djangoapps.agreements.serializers import IntegritySignatureSerializer
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled
def is_user_course_or_global_staff(user, course_id):
@@ -67,8 +67,7 @@ class IntegritySignatureView(AuthenticatedAPIView):
If a username is not given, it should default to the requesting user (or masqueraded user).
Only staff should be able to access this endpoint for other users.
"""
# check that waffle flag is enabled
if not is_integrity_signature_enabled(CourseKey.from_string(course_id)):
if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
return Response(
status=status.HTTP_404_NOT_FOUND,
)
@@ -111,8 +110,7 @@ class IntegritySignatureView(AuthenticatedAPIView):
created_at: "2021-04-23T18:25:43.511Z"
}
"""
# check that waffle flag is enabled
if not is_integrity_signature_enabled(CourseKey.from_string(course_id)):
if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
return Response(
status=status.HTTP_404_NOT_FOUND,
)

View File

@@ -43,7 +43,6 @@ from common.djangoapps.student.models import (
from common.djangoapps.student.roles import CourseInstructorRole
from common.djangoapps.student.tests.factories import CourseEnrollmentCelebrationFactory, UserFactory
from openedx.core.djangoapps.agreements.api import create_integrity_signature
from openedx.core.djangoapps.agreements.toggles import ENABLE_INTEGRITY_SIGNATURE
User = get_user_model()
@@ -362,7 +361,7 @@ class CourseApiTestViews(BaseCoursewareTests, MasqueradeMixin):
('audit', True, False, False),
)
@ddt.unpack
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, True)
@mock.patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True})
def test_user_needs_integrity_signature(
self, enrollment_mode, is_staff, has_integrity_signature, needs_integrity_signature,
):
@@ -398,7 +397,7 @@ class CourseApiTestViews(BaseCoursewareTests, MasqueradeMixin):
(3, True),
)
@ddt.unpack
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, True)
@mock.patch.dict(settings.FEATURES, {'ENABLE_INTEGRITY_SIGNATURE': True})
def test_course_staff_masquerade(self, masquerade_group_id, needs_signature):
self.user.is_staff = True
self.user.save()

View File

@@ -3,6 +3,7 @@ Course API Views
"""
from completion.exceptions import UnavailableCompletionData
from completion.utilities import get_key_to_last_completed_block
from django.conf import settings
from django.urls import reverse
from django.utils.translation import gettext as _
from edx_django_utils.cache import TieredCache
@@ -48,7 +49,6 @@ from lms.djangoapps.courseware.views.views import get_cert_data
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.agreements.api import get_integrity_signature
from openedx.core.djangoapps.agreements.toggles import is_integrity_signature_enabled as integrity_signature_toggle
from openedx.core.djangoapps.courseware_api.utils import get_celebrations_dict
from openedx.core.djangoapps.programs.utils import ProgramProgressMeter
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
@@ -319,15 +319,18 @@ class CoursewareMeta:
@property
def is_integrity_signature_enabled(self):
"""
Course waffle flag for the integrity signature feature.
Django setting for the integrity signature feature.
"""
return integrity_signature_toggle(self.course_key)
return settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE', False)
@property
def user_needs_integrity_signature(self):
"""
Boolean describing whether the user needs to sign the integrity agreement for a course.
"""
if not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'):
return False
integrity_signature_required = (
self.enrollment_object
# Master's enrollments are excluded here as honor code is handled separately
@@ -342,13 +345,11 @@ class CoursewareMeta:
self.course_masquerade
)
if (
integrity_signature_toggle(self.course_key)
and integrity_signature_required
):
if integrity_signature_required:
signature = get_integrity_signature(self.effective_user.username, str(self.course_key))
if not signature:
return True
return False
@property