From e1517223b9856eed714b80763d28378abd41007e Mon Sep 17 00:00:00 2001
From: Dillon Dumesnil <ddumesnil@edx.org>
Date: Wed, 16 Dec 2020 17:11:38 +0000
Subject: [PATCH] Fixes for Shift deadlines during masquerade in Learning MFE

The change to masquerade in the courseware view allows the proper
viewing of the xblock from the perspective of the masqueraded user.
In this case, it allows a staff user masquerading as a learner to see
their shift dates calls to action inside the MFE (the old view already
had this set up). The second change allows the staff user masquerading
to reset the schedule of the learner being masqueraded via the CTAs
---
 common/djangoapps/util/views.py               |  3 ++
 lms/djangoapps/courseware/views/views.py      |  5 ++-
 .../api/v1/tests/test_views.py                | 42 ++++++++++++++++---
 .../course_experience/api/v1/views.py         | 42 +++++++++++++++----
 4 files changed, 77 insertions(+), 15 deletions(-)

diff --git a/common/djangoapps/util/views.py b/common/djangoapps/util/views.py
index d7c1faf00bf..9bb769dc143 100644
--- a/common/djangoapps/util/views.py
+++ b/common/djangoapps/util/views.py
@@ -196,6 +196,9 @@ def reset_course_deadlines(request):
     """
     Set the start_date of a schedule to today, which in turn will adjust due dates for
     sequentials belonging to a self paced course
+
+    IMPORTANT NOTE: If updates are happening to the logic here, ALSO UPDATE the `reset_course_deadlines`
+    function in openedx/features/course_experience/api/v1/views.py as well.
     """
     course_key = CourseKey.from_string(request.POST.get('course_id'))
     _course_masquerade, user = setup_masquerade(
diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py
index 582438de501..72b634ba6bf 100644
--- a/lms/djangoapps/courseware/views/views.py
+++ b/lms/djangoapps/courseware/views/views.py
@@ -1631,6 +1631,9 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
             u"Rendering of the xblock view '{}' is not supported.".format(bleach.clean(requested_view, strip=True))
         )
 
+    staff_access = has_access(request.user, 'staff', course_key)
+    _course_masquerade, request.user = setup_masquerade(request, course_key, staff_access)
+
     with modulestore().bulk_operations(course_key):
         # verify the user has access to the course, including enrollment check
         try:
@@ -1668,7 +1671,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
             'disable_window_wrap': True,
             'enable_completion_on_view_service': enable_completion_on_view_service,
             'edx_notes_enabled': is_feature_enabled(course, request.user),
-            'staff_access': bool(request.user.has_perm(VIEW_XQA_INTERFACE, course)),
+            'staff_access': staff_access,
             'xqa_server': settings.FEATURES.get('XQA_SERVER', 'http://your_xqa_server.com'),
             'missed_deadlines': missed_deadlines,
             'missed_gated_content': missed_gated_content,
diff --git a/openedx/features/course_experience/api/v1/tests/test_views.py b/openedx/features/course_experience/api/v1/tests/test_views.py
index 795e61d74fe..7f18409aa3d 100644
--- a/openedx/features/course_experience/api/v1/tests/test_views.py
+++ b/openedx/features/course_experience/api/v1/tests/test_views.py
@@ -1,23 +1,29 @@
 """
 Tests for reset deadlines endpoint.
 """
+import datetime
 import ddt
 
 from django.urls import reverse
+from django.utils import timezone
+from mock import patch
 
 from common.djangoapps.course_modes.models import CourseMode
-from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
 from common.djangoapps.student.models import CourseEnrollment
+from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin
+from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
+from openedx.core.djangoapps.schedules.models import Schedule
+from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
+from xmodule.modulestore.tests.factories import CourseFactory
 
 
 @ddt.ddt
-class ResetCourseDeadlinesViewTests(BaseCourseHomeTests):
+class ResetCourseDeadlinesViewTests(BaseCourseHomeTests, MasqueradeMixin):
     """
     Tests for reset deadlines endpoint.
     """
-    @ddt.data(CourseMode.VERIFIED)
-    def test_reset_deadlines(self, enrollment_mode):
-        CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode)
+    def test_reset_deadlines(self):
+        CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
         # Test correct post body
         response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id})
         self.assertEqual(response.status_code, 200)
@@ -30,6 +36,32 @@ class ResetCourseDeadlinesViewTests(BaseCourseHomeTests):
         )
         self.assertEqual(response.status_code, 400)
 
+    def test_reset_deadlines_with_masquerade(self):
+        """ Staff users should be able to masquerade as a learner and reset the learner's schedule """
+        course = CourseFactory.create(self_paced=True)
+        student_username = self.user.username
+        student_enrollment = CourseEnrollment.enroll(self.user, course.id)
+        student_schedule = ScheduleFactory.create(
+            start_date=timezone.now() - datetime.timedelta(days=100),
+            enrollment=student_enrollment
+        )
+        staff_schedule = ScheduleFactory(
+            start_date=timezone.now() - datetime.timedelta(days=30),
+            enrollment__course__id=course.id,
+            enrollment__user=self.staff_user,
+        )
+
+        self.switch_to_staff()
+        self.update_masquerade(course=course, username=student_username)
+
+        with patch('openedx.features.course_experience.api.v1.views.dates_banner_should_display',
+                   return_value=(True, False)):
+            self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': course.id})
+        updated_schedule = Schedule.objects.get(id=student_schedule.id)
+        self.assertEqual(updated_schedule.start_date.date(), datetime.datetime.today().date())
+        updated_staff_schedule = Schedule.objects.get(id=staff_schedule.id)
+        self.assertEqual(updated_staff_schedule.start_date, staff_schedule.start_date)
+
     def test_post_unauthenticated_user(self):
         self.client.logout()
         response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id})
diff --git a/openedx/features/course_experience/api/v1/views.py b/openedx/features/course_experience/api/v1/views.py
index 676f2e2d5a4..41ba6803323 100644
--- a/openedx/features/course_experience/api/v1/views.py
+++ b/openedx/features/course_experience/api/v1/views.py
@@ -1,4 +1,7 @@
-import six
+"""
+Views for Course Experience API.
+"""
+import logging
 
 from django.conf import settings
 from django.urls import reverse
@@ -13,15 +16,20 @@ from rest_framework.generics import RetrieveAPIView
 
 from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
 from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
+from opaque_keys.edx.keys import CourseKey
 
 from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active
 from lms.djangoapps.course_home_api.utils import get_microfrontend_url
+from lms.djangoapps.courseware.access import has_access
 from lms.djangoapps.courseware.courses import get_course_with_access
+from lms.djangoapps.courseware.masquerade import setup_masquerade
 
-from opaque_keys.edx.keys import CourseKey
 from openedx.core.djangoapps.schedules.utils import reset_self_paced_schedule
 from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
 from openedx.features.course_experience.api.v1.serializers import CourseDeadlinesMobileSerializer
+from openedx.features.course_experience.utils import dates_banner_should_display
+
+log = logging.getLogger(__name__)
 
 
 class UnableToResetDeadlines(APIException):
@@ -36,6 +44,13 @@ class UnableToResetDeadlines(APIException):
 ))
 @permission_classes((IsAuthenticated,))
 def reset_course_deadlines(request):
+    """
+    Set the start_date of a schedule to today, which in turn will adjust due dates for
+    sequentials belonging to a self paced course
+
+    IMPORTANT NOTE: If updates are happening to the logic here, ALSO UPDATE the `reset_course_deadlines`
+    function in common/djangoapps/util/views.py as well.
+    """
     course_key = request.data.get('course_key', None)
 
     # If body doesnt contain 'course_key', return 400 to client.
@@ -47,13 +62,21 @@ def reset_course_deadlines(request):
         raise ParseError(_("Only 'course_key' is expected."))
 
     try:
-        reset_self_paced_schedule(request.user, course_key)
-
-        key = CourseKey.from_string(course_key)
-        if course_home_mfe_dates_tab_is_active(key):
-            body_link = get_microfrontend_url(course_key=course_key, view_name='dates')
+        course_key = CourseKey.from_string(course_key)
+        _course_masquerade, user = setup_masquerade(
+            request,
+            course_key,
+            has_access(request.user, 'staff', course_key)
+        )
+
+        missed_deadlines, missed_gated_content = dates_banner_should_display(course_key, user)
+        if missed_deadlines and not missed_gated_content:
+            reset_self_paced_schedule(user, course_key)
+
+        if course_home_mfe_dates_tab_is_active(course_key):
+            body_link = get_microfrontend_url(course_key=str(course_key), view_name='dates')
         else:
-            body_link = '{}{}'.format(settings.LMS_ROOT_URL, reverse('dates', args=[six.text_type(course_key)]))
+            body_link = '{}{}'.format(settings.LMS_ROOT_URL, reverse('dates', args=[str(course_key)]))
 
         return Response({
             'body': format_html('<a href="{}">{}</a>', body_link, _('View all dates')),
@@ -62,7 +85,8 @@ def reset_course_deadlines(request):
             'link_text': _('View all dates'),
             'message': _('Deadlines successfully reset.'),
         })
-    except Exception:
+    except Exception as e:
+        log.exception(e)
         raise UnableToResetDeadlines
 
 
-- 
GitLab