Esempio n. 1
0
 def _setstaff_login(self):
     """Makes the test user staff and logs them in"""
     GlobalStaff().add_users(self.user)
     self.client.login(username=self.user.username, password='******')
Esempio n. 2
0
 def test_request_global_staff_courses_using_scope(self):
     GlobalStaff().add_users(self.user)
     self._assert_role_using_scope('course_staff',
                                   'staff_courses',
                                   assert_one_course=False)
Esempio n. 3
0
 def test_request_global_staff_courses_with_claims(self):
     GlobalStaff().add_users(self.user)
     self._assert_role_using_claim('course_staff', 'staff_courses')
Esempio n. 4
0
def certificates_list_handler(request, course_key_string):
    """
    A RESTful handler for Course Certificates

    GET
        html: return Certificates list page (Backbone application)
    POST
        json: create new Certificate
    """
    course_key = CourseKey.from_string(course_key_string)
    store = modulestore()
    with store.bulk_operations(course_key):
        try:
            course = _get_course_and_check_access(course_key, request.user)
        except PermissionDenied:
            msg = _('PermissionDenied: Failed in authenticating {user}').format(user=request.user)
            return JsonResponse({"error": msg}, status=403)

        if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
            certificate_url = reverse_course_url('certificates.certificates_list_handler', course_key)
            course_outline_url = reverse_course_url('course_handler', course_key)
            upload_asset_url = reverse_course_url('assets_handler', course_key)
            activation_handler_url = reverse_course_url(
                handler_name='certificates.certificate_activation_handler',
                course_key=course_key
            )
            course_modes = [mode.slug for mode in CourseMode.modes_for_course(course.id)]
            certificate_web_view_url = get_lms_link_for_certificate_web_view(
                user_id=request.user.id,
                course_key=course_key,
                mode=course_modes[0]  # CourseMode.modes_for_course returns default mode 'honor' if doesn't find anyone.
            )
            certificates = None
            is_active = False
            if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
                certificates = CertificateManager.get_certificates(course)
                # we are assuming only one certificate in certificates collection.
                for certificate in certificates:
                    is_active = certificate.get('is_active', False)
                    break

            return render_to_response('certificates.html', {
                'context_course': course,
                'certificate_url': certificate_url,
                'course_outline_url': course_outline_url,
                'upload_asset_url': upload_asset_url,
                'certificates': json.dumps(certificates),
                'course_modes': course_modes,
                'certificate_web_view_url': certificate_web_view_url,
                'is_active': is_active,
                'is_global_staff': GlobalStaff().has_user(request.user),
                'certificate_activation_handler_url': activation_handler_url
            })
        elif "application/json" in request.META.get('HTTP_ACCEPT'):
            # Retrieve the list of certificates for the specified course
            if request.method == 'GET':
                certificates = CertificateManager.get_certificates(course)
                return JsonResponse(certificates, encoder=EdxJSONEncoder)
            elif request.method == 'POST':
                # Add a new certificate to the specified course
                try:
                    new_certificate = CertificateManager.deserialize_certificate(course, request.body)
                except CertificateValidationError as err:
                    return JsonResponse({"error": err.message}, status=400)
                if course.certificates.get('certificates') is None:
                    course.certificates['certificates'] = []
                course.certificates['certificates'].append(new_certificate.certificate_data)
                response = JsonResponse(CertificateManager.serialize_certificate(new_certificate), status=201)
                response["Location"] = reverse_course_url(
                    'certificates.certificates_detail_handler',
                    course.id,
                    kwargs={'certificate_id': new_certificate.id}
                )
                store.update_item(course, request.user.id)
                CertificateManager.track_event('created', {
                    'course_id': unicode(course.id),
                    'configuration_id': new_certificate.id
                })
                course = _get_course_and_check_access(course_key, request.user)
                return response
        else:
            return HttpResponse(status=406)
Esempio n. 5
0
def certificates_detail_handler(request, course_key_string, certificate_id):
    """
    JSON API endpoint for manipulating a course certificate via its internal identifier.
    Utilized by the Backbone.js 'certificates' application model

    POST or PUT
        json: update the specified certificate based on provided information
    DELETE
        json: remove the specified certificate from the course
    """
    course_key = CourseKey.from_string(course_key_string)
    course = _get_course_and_check_access(course_key, request.user)

    certificates_list = course.certificates.get('certificates', [])
    match_index = None
    match_cert = None
    for index, cert in enumerate(certificates_list):
        if certificate_id is not None:
            if int(cert['id']) == int(certificate_id):
                match_index = index
                match_cert = cert

    store = modulestore()
    if request.method in ('POST', 'PUT'):
        if certificate_id:
            active_certificates = CertificateManager.get_certificates(course, only_active=True)
            if int(certificate_id) in [int(certificate["id"]) for certificate in active_certificates]:
                # Only global staff (PMs) are able to edit active certificate configuration
                if not GlobalStaff().has_user(request.user):
                    raise PermissionDenied()
        try:
            new_certificate = CertificateManager.deserialize_certificate(course, request.body)
        except CertificateValidationError as err:
            return JsonResponse({"error": err.message}, status=400)

        serialized_certificate = CertificateManager.serialize_certificate(new_certificate)
        cert_event_type = 'created'
        if match_cert:
            cert_event_type = 'modified'
            certificates_list[match_index] = serialized_certificate
        else:
            certificates_list.append(serialized_certificate)

        store.update_item(course, request.user.id)
        CertificateManager.track_event(cert_event_type, {
            'course_id': unicode(course.id),
            'configuration_id': serialized_certificate["id"]
        })
        return JsonResponse(serialized_certificate, status=201)

    elif request.method == "DELETE":
        if not match_cert:
            return JsonResponse(status=404)

        active_certificates = CertificateManager.get_certificates(course, only_active=True)
        if int(certificate_id) in [int(certificate["id"]) for certificate in active_certificates]:
            # Only global staff (PMs) are able to delete active certificate configuration
            if not GlobalStaff().has_user(request.user):
                raise PermissionDenied()

        CertificateManager.remove_certificate(
            request=request,
            store=store,
            course=course,
            certificate_id=certificate_id
        )
        CertificateManager.track_event('deleted', {
            'course_id': unicode(course.id),
            'configuration_id': certificate_id
        })
        return JsonResponse(status=204)
Esempio n. 6
0
 def check_support():
     """Check that the user has access to the support UI. """
     if perm != 'global':
         return ACCESS_DENIED
     return (ACCESS_GRANTED if GlobalStaff().has_user(user)
             or SupportStaffRole().has_user(user) else ACCESS_DENIED)
Esempio n. 7
0
 def set_staff(self, create, extracted, **kwargs):
     GlobalStaff().add_users(self)
Esempio n. 8
0
 def check_staff():
     if perm != 'global':
         debug("Deny: invalid permission '%s'", perm)
         return False
     return GlobalStaff().has_user(user)
Esempio n. 9
0
    def get_blacklist_of_fields(cls, course_key):
        """
        Returns a list of fields to not include in Studio Advanced settings based on a
        feature flag (i.e. enabled or disabled).
        """
        # Copy the filtered list to avoid permanently changing the class attribute.
        black_list = list(cls.FIELDS_BLACK_LIST)

        # Do not show giturl if feature is not enabled.
        if not settings.FEATURES.get('ENABLE_EXPORT_GIT'):
            black_list.append('giturl')

        # Do not show edxnotes if the feature is disabled.
        if not settings.FEATURES.get('ENABLE_EDXNOTES'):
            black_list.append('edxnotes')

        # Do not show video auto advance if the feature is disabled
        if not settings.FEATURES.get('ENABLE_OTHER_COURSE_SETTINGS'):
            black_list.append('other_course_settings')

        # Do not show video_upload_pipeline if the feature is disabled.
        if not settings.FEATURES.get('ENABLE_VIDEO_UPLOAD_PIPELINE'):
            black_list.append('video_upload_pipeline')

        # Do not show video auto advance if the feature is disabled
        if not settings.FEATURES.get('ENABLE_AUTOADVANCE_VIDEOS'):
            black_list.append('video_auto_advance')

        # Do not show social sharing url field if the feature is disabled.
        if (not hasattr(settings, 'SOCIAL_SHARING_SETTINGS') or
                not getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}).get("CUSTOM_COURSE_URLS")):
            black_list.append('social_sharing_url')

        # Do not show teams configuration if feature is disabled.
        if not settings.FEATURES.get('ENABLE_TEAMS'):
            black_list.append('teams_configuration')

        if not settings.FEATURES.get('ENABLE_VIDEO_BUMPER'):
            black_list.append('video_bumper')

        # Do not show enable_ccx if feature is not enabled.
        if not settings.FEATURES.get('CUSTOM_COURSES_EDX'):
            black_list.append('enable_ccx')
            black_list.append('ccx_connector')

        # Do not show "Issue Open Badges" in Studio Advanced Settings
        # if the feature is disabled.
        if not settings.FEATURES.get('ENABLE_OPENBADGES'):
            black_list.append('issue_badges')

        # If the XBlockStudioConfiguration table is not being used, there is no need to
        # display the "Allow Unsupported XBlocks" setting.
        if not XBlockStudioConfigurationFlag.is_enabled():
            black_list.append('allow_unsupported_xblocks')

        # If the ENABLE_PROCTORING_PROVIDER_OVERRIDES waffle flag is not enabled,
        # do not show "Proctoring Configuration" in Studio Advanced Settings.
        if not ENABLE_PROCTORING_PROVIDER_OVERRIDES.is_enabled(course_key):
            black_list.append('proctoring_provider')

        # Do not show "Course Visibility For Unenrolled Learners" in Studio Advanced Settings
        # if the enable_anonymous_access flag is not enabled
        if not COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key=course_key):
            black_list.append('course_visibility')

        # Do not show "Create Zendesk Tickets For Suspicious Proctored Exam Attempts" in
        # Studio Advanced Settings if the user is not edX staff.
        if not GlobalStaff().has_user(get_current_user()):
            black_list.append('create_zendesk_tickets')

        return black_list
Esempio n. 10
0
    def post(self, request):
        # pylint: disable=too-many-statements
        """Enrolls the currently logged-in user in a course.

        Server-to-server calls may deactivate or modify the mode of existing enrollments. All other requests
        go through `add_enrollment()`, which allows creation of new and reactivation of old enrollments.
        """
        # Get the User, Course ID, and Mode from the request.

        username = request.data.get('user', request.user.username)
        course_id = request.data.get('course_details', {}).get('course_id')

        if not course_id:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={"message": u"Course ID must be specified to create a new enrollment."}
            )

        try:
            course_id = CourseKey.from_string(course_id)
        except InvalidKeyError:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id)
                }
            )

        mode = request.data.get('mode')

        has_api_key_permissions = self.has_api_key_permissions(request)

        # Check that the user specified is either the same user, or this is a server-to-server request.
        if not username:
            username = request.user.username
        if username != request.user.username and not has_api_key_permissions \
                and not GlobalStaff().has_user(request.user):
            # Return a 404 instead of a 403 (Unauthorized). If one user is looking up
            # other users, do not let them deduce the existence of an enrollment.
            return Response(status=status.HTTP_404_NOT_FOUND)

        if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions \
                and not GlobalStaff().has_user(request.user):
            return Response(
                status=status.HTTP_403_FORBIDDEN,
                data={
                    "message": u"User does not have permission to create enrollment with mode [{mode}].".format(
                        mode=mode
                    )
                }
            )

        try:
            # Lookup the user, instead of using request.user, since request.user may not match the username POSTed.
            user = User.objects.get(username=username)
        except ObjectDoesNotExist:
            return Response(
                status=status.HTTP_406_NOT_ACCEPTABLE,
                data={
                    'message': u'The user {} does not exist.'.format(username)
                }
            )

        embargo_response = embargo_api.get_embargo_response(request, course_id, user)

        if embargo_response:
            return embargo_response

        try:
            is_active = request.data.get('is_active')
            # Check if the requested activation status is None or a Boolean
            if is_active is not None and not isinstance(is_active, bool):
                return Response(
                    status=status.HTTP_400_BAD_REQUEST,
                    data={
                        'message': (u"'{value}' is an invalid enrollment activation status.").format(value=is_active)
                    }
                )

            explicit_linked_enterprise = request.data.get('linked_enterprise_customer')
            if explicit_linked_enterprise and has_api_key_permissions and enterprise_enabled():
                enterprise_api_client = EnterpriseApiServiceClient()
                consent_client = ConsentApiServiceClient()
                try:
                    enterprise_api_client.post_enterprise_course_enrollment(username, text_type(course_id), None)
                except EnterpriseApiException as error:
                    log.exception(u"An unexpected error occurred while creating the new EnterpriseCourseEnrollment "
                                  u"for user [%s] in course run [%s]", username, course_id)
                    raise CourseEnrollmentError(text_type(error))
                kwargs = {
                    'username': username,
                    'course_id': text_type(course_id),
                    'enterprise_customer_uuid': explicit_linked_enterprise,
                }
                consent_client.provide_consent(**kwargs)

            enrollment_attributes = request.data.get('enrollment_attributes')
            enrollment = api.get_enrollment(username, text_type(course_id))
            mode_changed = enrollment and mode is not None and enrollment['mode'] != mode
            active_changed = enrollment and is_active is not None and enrollment['is_active'] != is_active
            missing_attrs = []
            if enrollment_attributes:
                actual_attrs = [
                    u"{namespace}:{name}".format(**attr)
                    for attr in enrollment_attributes
                ]
                missing_attrs = set(REQUIRED_ATTRIBUTES.get(mode, [])) - set(actual_attrs)
            if (GlobalStaff().has_user(request.user) or has_api_key_permissions) and (mode_changed or active_changed):
                if mode_changed and active_changed and not is_active:
                    # if the requester wanted to deactivate but specified the wrong mode, fail
                    # the request (on the assumption that the requester had outdated information
                    # about the currently active enrollment).
                    msg = u"Enrollment mode mismatch: active mode={}, requested mode={}. Won't deactivate.".format(
                        enrollment["mode"], mode
                    )
                    log.warning(msg)
                    return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg})

                if missing_attrs:
                    msg = u"Missing enrollment attributes: requested mode={} required attributes={}".format(
                        mode, REQUIRED_ATTRIBUTES.get(mode)
                    )
                    log.warning(msg)
                    return Response(status=status.HTTP_400_BAD_REQUEST, data={"message": msg})

                response = api.update_enrollment(
                    username,
                    text_type(course_id),
                    mode=mode,
                    is_active=is_active,
                    enrollment_attributes=enrollment_attributes,
                    # If we are updating enrollment by authorized api caller, we should allow expired modes
                    include_expired=has_api_key_permissions
                )
            else:
                # Will reactivate inactive enrollments.
                response = api.add_enrollment(
                    username,
                    text_type(course_id),
                    mode=mode,
                    is_active=is_active,
                    enrollment_attributes=enrollment_attributes
                )

            cohort_name = request.data.get('cohort')
            if cohort_name is not None:
                cohort = get_cohort_by_name(course_id, cohort_name)
                try:
                    add_user_to_cohort(cohort, user)
                except ValueError:
                    # user already in cohort, probably because they were un-enrolled and re-enrolled
                    log.exception('Cohort re-addition')
            email_opt_in = request.data.get('email_opt_in', None)
            if email_opt_in is not None:
                org = course_id.org
                update_email_opt_in(request.user, org, email_opt_in)

            log.info(u'The user [%s] has already been enrolled in course run [%s].', username, course_id)
            return Response(response)
        except CourseModeNotFoundError as error:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    "message": (
                        u"The [{mode}] course mode is expired or otherwise unavailable for course run [{course_id}]."
                    ).format(mode=mode, course_id=course_id),
                    "course_details": error.data
                })
        except CourseNotFoundError:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    "message": u"No course '{course_id}' found for enrollment".format(course_id=course_id)
                }
            )
        except CourseEnrollmentExistsError as error:
            log.warning(u'An enrollment already exists for user [%s] in course run [%s].', username, course_id)
            return Response(data=error.enrollment)
        except CourseEnrollmentError:
            log.exception(u"An error occurred while creating the new course enrollment for user "
                          u"[%s] in course run [%s]", username, course_id)
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    "message": (
                        u"An error occurred while creating the new course enrollment for user "
                        u"'{username}' in course '{course_id}'"
                    ).format(username=username, course_id=course_id)
                }
            )
        except CourseUserGroup.DoesNotExist:
            log.exception(u'Missing cohort [%s] in course run [%s]', cohort_name, course_id)
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    "message": u"An error occured while adding to cohort [%s]" % cohort_name
                })
        finally:
            # Assumes that the ecommerce service uses an API key to authenticate.
            if has_api_key_permissions:
                current_enrollment = api.get_enrollment(username, text_type(course_id))
                audit_log(
                    'enrollment_change_requested',
                    course_id=text_type(course_id),
                    requested_mode=mode,
                    actual_mode=current_enrollment['mode'] if current_enrollment else None,
                    requested_activation=is_active,
                    actual_activation=current_enrollment['is_active'] if current_enrollment else None,
                    user_id=user.id
                )
Esempio n. 11
0
 def test_global_staff(self):
     self.assertFalse(GlobalStaff().has_user(self.student))
     self.assertFalse(GlobalStaff().has_user(self.course_staff))
     self.assertFalse(GlobalStaff().has_user(self.course_instructor))
     self.assertTrue(GlobalStaff().has_user(self.global_staff))
Esempio n. 12
0
def certificates_list_handler(request, course_key_string):
    """
    A RESTful handler for Course Certificates

    GET
        html: return Certificates list page (Backbone application)
    POST
        json: create new Certificate
    """
    course_key = CourseKey.from_string(course_key_string)
    store = modulestore()
    with store.bulk_operations(course_key):
        try:
            course = _get_course_and_check_access(course_key, request.user)
        except PermissionDenied:
            msg = _(u'PermissionDenied: Failed in authenticating {user}').format(user=request.user)
            return JsonResponse({"error": msg}, status=403)

        if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
            certificate_url = reverse_course_url('certificates_list_handler', course_key)
            course_outline_url = reverse_course_url('course_handler', course_key)
            upload_asset_url = reverse_course_url('assets_handler', course_key)
            activation_handler_url = reverse_course_url(
                handler_name='certificate_activation_handler',
                course_key=course_key
            )
            course_modes = [
                mode.slug for mode in CourseMode.modes_for_course(
                    course_id=course.id, include_expired=True
                ) if mode.slug != 'audit'
            ]

            has_certificate_modes = len(course_modes) > 0

            if has_certificate_modes:
                certificate_web_view_url = get_lms_link_for_certificate_web_view(
                    course_key=course_key,
                    mode=course_modes[0]  # CourseMode.modes_for_course returns default mode if doesn't find anyone.
                )
            else:
                certificate_web_view_url = None

            is_active, certificates = CertificateManager.is_activated(course)

            course_authoring_microfrontend_url = get_proctored_exam_settings_url(course)

            return render_to_response('certificates.html', {
                'context_course': course,
                'certificate_url': certificate_url,
                'course_outline_url': course_outline_url,
                'upload_asset_url': upload_asset_url,
                'certificates': certificates,
                'has_certificate_modes': has_certificate_modes,
                'course_modes': course_modes,
                'certificate_web_view_url': certificate_web_view_url,
                'is_active': is_active,
                'is_global_staff': GlobalStaff().has_user(request.user),
                'certificate_activation_handler_url': activation_handler_url,
                'course_authoring_microfrontend_url': course_authoring_microfrontend_url,
            })
        elif "application/json" in request.META.get('HTTP_ACCEPT'):
            # Retrieve the list of certificates for the specified course
            if request.method == 'GET':
                certificates = CertificateManager.get_certificates(course)
                return JsonResponse(certificates, encoder=EdxJSONEncoder)
            elif request.method == 'POST':
                # Add a new certificate to the specified course
                try:
                    new_certificate = CertificateManager.deserialize_certificate(course, request.body)
                except CertificateValidationError as err:
                    return JsonResponse({"error": text_type(err)}, status=400)
                if course.certificates.get('certificates') is None:
                    course.certificates['certificates'] = []
                course.certificates['certificates'].append(new_certificate.certificate_data)
                response = JsonResponse(CertificateManager.serialize_certificate(new_certificate), status=201)
                response["Location"] = reverse_course_url(
                    'certificates_detail_handler',
                    course.id,
                    kwargs={'certificate_id': new_certificate.id}
                )
                store.update_item(course, request.user.id)
                CertificateManager.track_event('created', {
                    'course_id': six.text_type(course.id),
                    'configuration_id': new_certificate.id
                })
                course = _get_course_and_check_access(course_key, request.user)
                return response
        else:
            return HttpResponse(status=406)