Exemple #1
0
    def get(self, request, usage_key_string, *args, **kwargs):  # lint-amnesty, pylint: disable=unused-argument
        """
        Return response to a GET request.
        """
        try:
            usage_key = UsageKey.from_string(usage_key_string)
        except InvalidKeyError:
            raise NotFound(f"Invalid usage key: '{usage_key_string}'.")  # lint-amnesty, pylint: disable=raise-missing-from

        _, request.user = setup_masquerade(
            request,
            usage_key.course_key,
            staff_access=has_access(request.user, 'staff', usage_key.course_key),
            reset_masquerade_data=True,
        )

        sequence, _ = get_module_by_usage_id(
            self.request,
            str(usage_key.course_key),
            str(usage_key),
            disable_staff_debug_info=True,
            will_recheck_access=True)

        view = STUDENT_VIEW
        if request.user.is_anonymous:
            view = PUBLIC_VIEW

        context = {'specific_masquerade': is_masquerading_as_specific_student(request.user, usage_key.course_key)}
        return Response(sequence.get_metadata(view=view, context=context))
Exemple #2
0
    def get(self, request, usage_key_string, *args, **kwargs):  # lint-amnesty, pylint: disable=unused-argument
        """
        Return response to a GET request.
        """
        try:
            usage_key = UsageKey.from_string(usage_key_string)
        except InvalidKeyError as exc:
            raise NotFound(f"Invalid usage key: '{usage_key_string}'.") from exc
        _, request.user = setup_masquerade(
            request,
            usage_key.course_key,
            staff_access=has_access(request.user, 'staff', usage_key.course_key),
            reset_masquerade_data=True,
        )

        sequence, _ = get_module_by_usage_id(
            self.request,
            str(usage_key.course_key),
            str(usage_key),
            disable_staff_debug_info=True,
            will_recheck_access=True)

        if not hasattr(sequence, 'get_metadata'):
            # Looks like we were asked for metadata on something that is not a sequence (or section).
            return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY)

        view = STUDENT_VIEW
        if request.user.is_anonymous:
            view = PUBLIC_VIEW

        context = {'specific_masquerade': is_masquerading_as_specific_student(request.user, usage_key.course_key)}
        return Response(sequence.get_metadata(view=view, context=context))
Exemple #3
0
def has_full_access_role_in_masquerade(user, course_key):
    """
    The roles of the masquerade user are used to determine whether the content gate displays.

    Returns:
        True if we are masquerading as a full-access generic user
        False if we are masquerading as a non-full-access generic user
        None if we are not masquerading or masquerading as a specific student that should go through normal checks
    """
    # The masquerade module imports from us, so avoid a circular dependency here
    from lms.djangoapps.courseware.masquerade import (
        get_course_masquerade,
        is_masquerading_as_full_access,
        is_masquerading_as_non_audit_enrollment,
        is_masquerading_as_specific_student,
        is_masquerading_as_staff,
    )

    course_masquerade = get_course_masquerade(user, course_key)
    if not course_masquerade or is_masquerading_as_specific_student(
            user, course_key):
        return None
    return (is_masquerading_as_staff(user, course_key) or
            is_masquerading_as_full_access(user, course_key, course_masquerade)
            or is_masquerading_as_non_audit_enrollment(user, course_key,
                                                       course_masquerade))
Exemple #4
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None, user_partition=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        if user is None and enrollment is not None:
            user = enrollment.user

        course_masquerade = get_course_masquerade(user, course_key)
        no_masquerade = course_masquerade is None
        student_masquerade = is_masquerading_as_specific_student(user, course_key)
        user_variable_represents_correct_user = (no_masquerade or student_masquerade)

        if course_masquerade:
            if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade, student_masquerade,
                                                      user_partition):
                return False
        # When a request is not in a masquerade state the user variable represents the correct user.
        elif user and user.id and has_staff_roles(user, course_key):
            return False

        # check if user is in holdback
        if user_variable_represents_correct_user and is_in_holdback(user):
            return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        # Also, ignore enrollment creation date if the user is masquerading.
        if enrollment is None or course_masquerade:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)
Exemple #5
0
    def enabled_for_enrollment(cls, user=None, course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """

        if user is None or course_key is None:
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        enrollment = CourseEnrollment.get_enrollment(user, course_key, ['fbeenrollmentexclusion'])

        if user is None and enrollment is not None:
            user = enrollment.user

        if user and user.id:
            course_masquerade = get_course_masquerade(user, course_key)
            if course_masquerade:
                if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade):
                    return False
            elif has_staff_roles(user, course_key):
                return False

        is_masquerading = get_course_masquerade(user, course_key)
        no_masquerade = is_masquerading is None
        student_masquerade = is_masquerading_as_specific_student(user, course_key)

        # check if user is in holdback
        if (no_masquerade or student_masquerade) and is_in_holdback(user, enrollment):
            return False

        not_student_masquerade = is_masquerading and not student_masquerade

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        # When masquerading as a user group rather than a specific learner,
        # course duration limits will be on if they are on for the course.
        # When masquerading as a specific learner, course duration limits
        # will be on if they are currently on for the learner.
        if enrollment is None or not_student_masquerade:
            # we bypass enabled_for_course here and use enabled_as_of_datetime directly
            # because the correct_modes_for_fbe for FBE check contained in enabled_for_course
            # is redundant with checks done upstream of this code
            target_datetime = timezone.now()
        else:
            target_datetime = enrollment.created
        current_config = cls.current(course_key=course_key)
        return current_config.enabled_as_of_datetime(target_datetime=target_datetime)
Exemple #6
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(user)
            is_in_holdback = False
            no_masquerade = get_course_masquerade(user, course_key) is None
            student_masquerade = is_masquerading_as_specific_student(user, course_key)
            if user and (no_masquerade or student_masquerade):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)
Exemple #7
0
    def get_group_for_user(cls, course_key, user, user_partition, **kwargs):  # pylint: disable=unused-argument
        """
        Returns the Group for the specified user.
        """

        # First, check if we have to deal with masquerading.
        # If the current user is masquerading as a specific student, use the
        # same logic as normal to return that student's group. If the current
        # user is masquerading as a generic student in a specific group, then
        # return that group.
        if get_course_masquerade(user, course_key) and not is_masquerading_as_specific_student(user, course_key):
            return get_masquerading_user_group(course_key, user, user_partition)

        # For now, treat everyone as a Full-access user, until we have the rest of the
        # feature gating logic in place.

        if not CONTENT_TYPE_GATING_FLAG.is_enabled():
            return cls.FULL_ACCESS

        # If CONTENT_TYPE_GATING is enabled use the following logic to determine whether a user should have FULL_ACCESS
        # or LIMITED_ACCESS

        course_mode = apps.get_model('course_modes.CourseMode')
        modes = course_mode.modes_for_course(course_key, include_expired=True, only_selectable=False)
        modes_dict = {mode.slug: mode for mode in modes}

        # If there is no verified mode, all users are granted FULL_ACCESS
        if not course_mode.has_verified_mode(modes_dict):
            return cls.FULL_ACCESS

        course_enrollment = apps.get_model('student.CourseEnrollment')

        mode_slug, is_active = course_enrollment.enrollment_mode_for_user(user, course_key)

        if mode_slug and is_active:
            course_mode = course_mode.mode_for_course(
                course_key,
                mode_slug,
                modes=modes,
            )
            if course_mode is None:
                LOG.error(
                    "User %s is in an unknown CourseMode '%s'"
                    " for course %s. Granting full access to content for this user",
                    user.username,
                    mode_slug,
                    course_key,
                )
                return cls.FULL_ACCESS

            if mode_slug == CourseMode.AUDIT:
                return cls.LIMITED_ACCESS
            else:
                return cls.FULL_ACCESS
        else:
            # Unenrolled users don't get gated content
            return cls.LIMITED_ACCESS
Exemple #8
0
    def get_group_for_user(cls, course_key, user, user_partition, **kwargs):  # pylint: disable=unused-argument
        """
        Returns the Group from the specified user partition to which the user
        is assigned, via enrollment mode. If a user is in a Credit mode, the Verified or
        Professional mode for the course is returned instead.

        If a course is using the Verified Track Cohorting pilot feature, this method
        returns None regardless of the user's enrollment mode.
        """
        if is_course_using_cohort_instead(course_key):
            return None

        # First, check if we have to deal with masquerading.
        # If the current user is masquerading as a specific student, use the
        # same logic as normal to return that student's group. If the current
        # user is masquerading as a generic student in a specific group, then
        # return that group.
        if get_course_masquerade(
                user, course_key) and not is_masquerading_as_specific_student(
                    user, course_key):
            return get_masquerading_user_group(course_key, user,
                                               user_partition)

        mode_slug, is_active = CourseEnrollment.enrollment_mode_for_user(
            user, course_key)
        if mode_slug and is_active:
            course_mode = CourseMode.mode_for_course(
                course_key,
                mode_slug,
                modes=CourseMode.modes_for_course(course_key,
                                                  include_expired=True,
                                                  only_selectable=False),
            )
            if course_mode and CourseMode.is_credit_mode(course_mode):
                # We want the verified track even if the upgrade deadline has passed, since we
                # are determining what content to show the user, not whether the user can enroll
                # in the verified track.
                course_mode = CourseMode.verified_mode_for_course(
                    course_key, include_expired=True)
            if not course_mode:
                course_mode = CourseMode.DEFAULT_MODE
            return Group(ENROLLMENT_GROUP_IDS[course_mode.slug]["id"],
                         six.text_type(course_mode.name))
        else:
            return None
Exemple #9
0
    def get_group_for_user(cls, course_key, user, user_partition, **kwargs):  # pylint: disable=unused-argument
        """
        Returns the Group for the specified user.
        """

        # First, check if we have to deal with masquerading.
        # If the current user is masquerading as a specific student, use the
        # same logic as normal to return that student's group. If the current
        # user is masquerading as a generic student in a specific group, then
        # return that group.
        if get_course_masquerade(
                user, course_key) and not is_masquerading_as_specific_student(
                    user, course_key):
            return get_masquerading_user_group(course_key, user,
                                               user_partition)

        # For now, treat everyone as a Full-access user, until we have the rest of the
        # feature gating logic in place.
        return cls.FULL_ACCESS
def get_access_expiration_data(user, course):
    """
    Create a dictionary of information about the access expiration for this user & course.

    Used by serializers to pass onto frontends and by the LMS locally to generate HTML for template rendering.

    Returns a dictionary of data, or None if no expiration is applicable.
    """
    expiration_date = get_user_course_expiration_date(user, course)
    if not expiration_date:
        return None

    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    if enrollment is None:
        return None

    now = timezone.now()
    upgrade_deadline = enrollment.upgrade_deadline
    if not upgrade_deadline or upgrade_deadline < now:
        upgrade_deadline = enrollment.course_upgrade_deadline
    if upgrade_deadline and upgrade_deadline < now:
        upgrade_deadline = None

    masquerading_expired_course = is_masquerading_as_specific_student(
        user, course.id) and expiration_date < now

    return {
        'expiration_date':
        expiration_date,
        'masquerading_expired_course':
        masquerading_expired_course,
        'upgrade_deadline':
        upgrade_deadline,
        'upgrade_url':
        verified_upgrade_deadline_link(user, course=course)
        if upgrade_deadline else None,
    }
Exemple #11
0
def generate_course_expired_message(user, course):
    """
    Generate the message for the user course expiration date if it exists.
    """
    if not CourseDurationLimitConfig.enabled_for_enrollment(
            user=user, course_key=course.id):
        return

    expiration_date = get_user_course_expiration_date(user, course)
    if not expiration_date:
        return

    if is_masquerading_as_specific_student(
            user, course.id) and timezone.now() > expiration_date:
        upgrade_message = _(
            'This learner does not have access to this course. '
            u'Their access expired on {expiration_date}.')
        return HTML(upgrade_message).format(expiration_date=strftime_localized(
            expiration_date, EXPIRATION_DATE_FORMAT_STR))
    else:
        enrollment = CourseEnrollment.get_enrollment(user, course.id)
        if enrollment is None:
            return

        upgrade_deadline = enrollment.upgrade_deadline
        now = timezone.now()
        course_upgrade_deadline = enrollment.course_upgrade_deadline
        if (not upgrade_deadline) or (upgrade_deadline < now):
            upgrade_deadline = course_upgrade_deadline

        expiration_message = _(
            u'{strong_open}Audit Access Expires {expiration_date}{strong_close}'
            u'{line_break}You lose all access to this course, including your progress, on '
            u'{expiration_date}.')
        upgrade_deadline_message = _(
            u'{line_break}Upgrade by {upgrade_deadline} to get unlimited access to the course '
            u'as long as it exists on the site. {a_open}Upgrade now{sronly_span_open} to '
            u'retain access past {expiration_date}{span_close}{a_close}')
        full_message = expiration_message
        if upgrade_deadline and now < upgrade_deadline:
            full_message += upgrade_deadline_message
            using_upgrade_messaging = True
        else:
            using_upgrade_messaging = False

        language = get_language()
        date_string = get_date_string()
        formatted_expiration_date = date_string.format(
            language=language,
            formatted_date=expiration_date.strftime("%Y-%m-%d"),
            formatted_date_localized=strftime_localized(
                expiration_date, EXPIRATION_DATE_FORMAT_STR))
        if using_upgrade_messaging:
            formatted_upgrade_deadline = date_string.format(
                language=language,
                formatted_date=upgrade_deadline.strftime("%Y-%m-%d"),
                formatted_date_localized=strftime_localized(
                    upgrade_deadline, EXPIRATION_DATE_FORMAT_STR))

            return HTML(full_message).format(
                a_open=HTML(u'<a href="{upgrade_link}">').format(
                    upgrade_link=verified_upgrade_deadline_link(
                        user=user, course=course)),
                sronly_span_open=HTML('<span class="sr-only">'),
                span_close=HTML('</span>'),
                a_close=HTML('</a>'),
                expiration_date=HTML(formatted_expiration_date),
                strong_open=HTML('<strong>'),
                strong_close=HTML('</strong>'),
                line_break=HTML('<br>'),
                upgrade_deadline=HTML(formatted_upgrade_deadline))

        else:
            return HTML(full_message).format(
                span_close=HTML('</span>'),
                expiration_date=HTML(formatted_expiration_date),
                strong_open=HTML('<strong>'),
                strong_close=HTML('</strong>'),
                line_break=HTML('<br>'),
            )
Exemple #12
0
    def enabled_for_enrollment(cls,
                               enrollment=None,
                               user=None,
                               course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None
                                       or course_key is not None):
            raise ValueError(
                'Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError(
                'Both user and course_key must be specified if no enrollment is provided'
            )

        if enrollment is None and user is None and course_key is None:
            raise ValueError(
                'At least one of enrollment or user and course_key must be specified'
            )

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # if the user is has a role of staff, instructor or beta tester their access should not expire
        if user is None and enrollment is not None:
            user = enrollment.user

        if user:
            course_masquerade = get_course_masquerade(user, course_key)
            if course_masquerade:
                verified_mode_id = settings.COURSE_ENROLLMENT_MODES.get(
                    CourseMode.VERIFIED, {}).get('id')
                is_verified = (course_masquerade.user_partition_id
                               == ENROLLMENT_TRACK_PARTITION_ID and
                               course_masquerade.group_id == verified_mode_id)
                is_full_access = (
                    course_masquerade.user_partition_id
                    == CONTENT_GATING_PARTITION_ID
                    and course_masquerade.group_id
                    == CONTENT_TYPE_GATE_GROUP_IDS['full_access'])
                is_staff = get_masquerade_role(user, course_key) == 'staff'
                if is_verified or is_full_access or is_staff:
                    return False
            else:
                staff_role = CourseStaffRole(course_key).has_user(user)
                instructor_role = CourseInstructorRole(course_key).has_user(
                    user)
                beta_tester_role = CourseBetaTesterRole(course_key).has_user(
                    user)

                if staff_role or instructor_role or beta_tester_role:
                    return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key,
                                          target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(
                user)
            is_in_holdback = False
            no_masquerade = get_course_masquerade(user, course_key) is None
            student_masquerade = is_masquerading_as_specific_student(
                user, course_key)
            if user and (no_masquerade or student_masquerade):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(
                target_datetime=enrollment.created)
Exemple #13
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """

        if FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG.is_enabled():
            return False

        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        if user is None and enrollment is not None:
            user = enrollment.user

        if user and user.id:
            course_masquerade = get_course_masquerade(user, course_key)
            if course_masquerade:
                if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade):
                    return False
            elif has_staff_roles(user, course_key):
                return False

        is_masquerading = get_course_masquerade(user, course_key)
        no_masquerade = is_masquerading is None
        student_masquerade = is_masquerading_as_specific_student(user, course_key)

        # check if user is in holdback
        if (no_masquerade or student_masquerade) and is_in_holdback(user):
            return False

        not_student_masquerade = is_masquerading and not student_masquerade

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        # When masquerading as a user group rather than a specific learner,
        # course duration limits will be on if they are on for the course.
        # When masquerading as a specific learner, course duration limits
        # will be on if they are currently on for the learner.
        if enrollment is None or not_student_masquerade:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)
Exemple #14
0
    def get_group_for_user(cls, course_key, user, user_partition, use_cached=True):
        """
        Returns the Group from the specified user partition to which the user
        is assigned, via their cohort membership and any mappings from cohorts
        to partitions / groups that might exist.

        If the user has not yet been assigned to a cohort, an assignment *might*
        be created on-the-fly, as determined by the course's cohort config.
        Any such side-effects will be triggered inside the call to
        cohorts.get_cohort().

        If the user has no cohort mapping, or there is no (valid) cohort ->
        partition group mapping found, the function returns None.
        """
        # First, check if we have to deal with masquerading.
        # If the current user is masquerading as a specific student, use the
        # same logic as normal to return that student's group. If the current
        # user is masquerading as a generic student in a specific group, then
        # return that group.
        if get_course_masquerade(user, course_key) and not is_masquerading_as_specific_student(user, course_key):
            return get_masquerading_user_group(course_key, user, user_partition)

        cohort = get_cohort(user, course_key, use_cached=use_cached)
        if cohort is None:
            # student doesn't have a cohort
            return None

        group_id, partition_id = get_group_info_for_cohort(cohort, use_cached=use_cached)
        if partition_id is None:
            # cohort isn't mapped to any partition group.
            return None

        if partition_id != user_partition.id:
            # if we have a match but the partition doesn't match the requested
            # one it means the mapping is invalid.  the previous state of the
            # partition configuration may have been modified.
            log.warn(
                u"partition mismatch in CohortPartitionScheme: %r",
                {
                    "requested_partition_id": user_partition.id,
                    "found_partition_id": partition_id,
                    "found_group_id": group_id,
                    "cohort_id": cohort.id,
                }
            )
            # fail silently
            return None

        try:
            return user_partition.get_group(group_id)
        except NoSuchUserPartitionGroupError:
            # if we have a match but the group doesn't exist in the partition,
            # it means the mapping is invalid.  the previous state of the
            # partition configuration may have been modified.
            log.warn(
                u"group not found in CohortPartitionScheme: %r",
                {
                    "requested_partition_id": user_partition.id,
                    "requested_group_id": group_id,
                    "cohort_id": cohort.id,
                },
                exc_info=True
            )
            # fail silently
            return None
Exemple #15
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None, user_partition=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        if user is None and enrollment is not None:
            user = enrollment.user

        course_masquerade = get_course_masquerade(user, course_key)
        no_masquerade = course_masquerade is None
        student_masquerade = is_masquerading_as_specific_student(user, course_key)
        user_variable_represents_correct_user = (no_masquerade or student_masquerade)

        if course_masquerade:
            if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade, student_masquerade,
                                                      user_partition):
                return False
        # When a request is not in a masquerade state the user variable represents the correct user.
        elif user and user.id and has_staff_roles(user, course_key):
            return False

        # check if user is in holdback
        if user_variable_represents_correct_user and is_in_holdback(user):
            return False

        if not correct_modes_for_fbe(course_key, enrollment, user):
            return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        # Also, ignore enrollment creation date if the user is masquerading.
        if enrollment is None or course_masquerade:
            target_datetime = timezone.now()
        else:
            target_datetime = enrollment.created
        current_config = cls.current(course_key=course_key)
        return current_config.enabled_as_of_datetime(target_datetime=target_datetime)
Exemple #16
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        if user is None and enrollment is not None:
            user = enrollment.user

        if user and user.id:
            course_masquerade = get_course_masquerade(user, course_key)
            if course_masquerade:
                if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade):
                    return False
            elif has_staff_roles(user, course_key):
                return False

        is_masquerading = get_course_masquerade(user, course_key)
        no_masquerade = is_masquerading is None
        student_masquerade = is_masquerading_as_specific_student(user, course_key)

        # check if user is in holdback
        if (no_masquerade or student_masquerade) and is_in_holdback(user):
            return False

        not_student_masquerade = is_masquerading and not student_masquerade

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        # When masquerading as a user group rather than a specific learner,
        # course duration limits will be on if they are on for the course.
        # When masquerading as a specific learner, course duration limits
        # will be on if they are currently on for the learner.
        if enrollment is None or not_student_masquerade:
            # we bypass enabled_for_course here and use enabled_as_of_datetime directly
            # because the correct_modes_for_fbe for FBE check contained in enabled_for_course
            # is redundant with checks done upstream of this code
            target_datetime = timezone.now()
        else:
            target_datetime = enrollment.created
        current_config = cls.current(course_key=course_key)
        return current_config.enabled_as_of_datetime(target_datetime=target_datetime)
Exemple #17
0
def register_course_expired_message(request, course):
    """
    Add a banner notifying the user of the user course expiration date if it exists.
    """
    if not CourseDurationLimitConfig.enabled_for_enrollment(
            user=request.user, course_key=course.id):
        return

    expiration_date = get_user_course_expiration_date(request.user, course)
    if not expiration_date:
        return

    if is_masquerading_as_specific_student(
            request.user, course.id) and timezone.now() > expiration_date:
        upgrade_message = _(
            'This learner does not have access to this course. '
            'Their access expired on {expiration_date}.')
        PageLevelMessages.register_warning_message(
            request,
            HTML(upgrade_message).format(expiration_date=strftime_localized(
                expiration_date, '%b. %-d, %Y')))
    else:
        enrollment = CourseEnrollment.get_enrollment(request.user, course.id)
        if enrollment is None:
            return

        upgrade_deadline = enrollment.upgrade_deadline
        now = timezone.now()
        course_upgrade_deadline = enrollment.course_upgrade_deadline
        if (not upgrade_deadline) or (upgrade_deadline < now):
            upgrade_deadline = course_upgrade_deadline

        expiration_message = _(
            '{strong_open}Audit Access Expires {expiration_date}{strong_close}'
            '{line_break}You lose all access to this course, including your progress, on '
            '{expiration_date}.')
        upgrade_deadline_message = _(
            '{line_break}Upgrade by {upgrade_deadline} to get unlimited access to the course '
            'as long as it exists on the site. {a_open}Upgrade now{sronly_span_open} to '
            'retain access past {expiration_date}{span_close}{a_close}')
        full_message = expiration_message
        if upgrade_deadline and now < upgrade_deadline:
            full_message += upgrade_deadline_message
            using_upgrade_messaging = True
        else:
            using_upgrade_messaging = False

        language = get_language()
        language_is_es = language and language.split('-')[0].lower() == 'es'
        if language_is_es:
            formatted_expiration_date = strftime_localized(
                expiration_date, '%-d de %b. de %Y').lower()
        else:
            formatted_expiration_date = strftime_localized(
                expiration_date, '%b. %-d, %Y')

        if using_upgrade_messaging:
            if language_is_es:
                formatted_upgrade_deadline = strftime_localized(
                    upgrade_deadline, '%-d de %b. de %Y').lower()
            else:
                formatted_upgrade_deadline = strftime_localized(
                    upgrade_deadline, '%b. %-d, %Y')

            PageLevelMessages.register_info_message(
                request,
                Text(full_message).format(
                    a_open=HTML('<a href="{upgrade_link}">').format(
                        upgrade_link=verified_upgrade_deadline_link(
                            user=request.user, course=course)),
                    sronly_span_open=HTML('<span class="sr-only">'),
                    span_close=HTML('</span>'),
                    a_close=HTML('</a>'),
                    expiration_date=formatted_expiration_date,
                    strong_open=HTML('<strong>'),
                    strong_close=HTML('</strong>'),
                    line_break=HTML('<br>'),
                    upgrade_deadline=formatted_upgrade_deadline))
        else:
            PageLevelMessages.register_info_message(
                request,
                Text(full_message).format(
                    span_close=HTML('</span>'),
                    expiration_date=formatted_expiration_date,
                    strong_open=HTML('<strong>'),
                    strong_close=HTML('</strong>'),
                    line_break=HTML('<br>'),
                ))
Exemple #18
0
    def enabled_for_enrollment(cls,
                               enrollment=None,
                               user=None,
                               course_key=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None
                                       or course_key is not None):
            raise ValueError(
                'Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError(
                'Both user and course_key must be specified if no enrollment is provided'
            )

        if enrollment is None and user is None and course_key is None:
            raise ValueError(
                'At least one of enrollment or user and course_key must be specified'
            )

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        if user is None and enrollment is not None:
            user = enrollment.user

        no_masquerade = get_course_masquerade(user, course_key) is None
        student_masquerade = is_masquerading_as_specific_student(
            user, course_key)
        # We can only use the user variable for the code below when the request is not in a masquerade state
        # or is masquerading as a specific user.
        # When a request is not in a masquerade state the user variable represents the correct user.
        # When a request is in a masquerade state and not masquerading as a specific user,
        # then then user variable will be the incorrect (original) user, not the masquerade user.
        # If a request is masquerading as a specific user, the user variable will represent the correct user.
        user_variable_represents_correct_user = (no_masquerade
                                                 or student_masquerade)
        if user and user.id:
            # TODO: Move masquerade checks to enabled_for_enrollment from content_type_gating/partitions.py
            # TODO: Consolidate masquerade checks into shared function like has_staff_roles below
            if user_variable_represents_correct_user and has_staff_roles(
                    user, course_key):
                return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key,
                                          target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(
                user)
            is_in_holdback = False
            if user and (user_variable_represents_correct_user):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(
                target_datetime=enrollment.created)
Exemple #19
0
def generate_course_expired_message(user, course):
    """
    Generate the message for the user course expiration date if it exists.
    """
    if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id):
        return

    expiration_date = get_user_course_expiration_date(user, course)
    if not expiration_date:
        return

    if is_masquerading_as_specific_student(user, course.id) and timezone.now() > expiration_date:
        upgrade_message = _('This learner does not have access to this course. '
                            u'Their access expired on {expiration_date}.')
        return HTML(upgrade_message).format(
            expiration_date=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR)
        )
    else:
        enrollment = CourseEnrollment.get_enrollment(user, course.id)
        if enrollment is None:
            return

        upgrade_deadline = enrollment.upgrade_deadline
        now = timezone.now()
        course_upgrade_deadline = enrollment.course_upgrade_deadline
        if (not upgrade_deadline) or (upgrade_deadline < now):
            upgrade_deadline = course_upgrade_deadline

        expiration_message = _(u'{strong_open}Audit Access Expires {expiration_date}{strong_close}'
                               u'{line_break}You lose all access to this course, including your progress, on '
                               u'{expiration_date}.')
        upgrade_deadline_message = _(u'{line_break}Upgrade by {upgrade_deadline} to get unlimited access to the course '
                                     u'as long as it exists on the site. {a_open}Upgrade now{sronly_span_open} to '
                                     u'retain access past {expiration_date}{span_close}{a_close}')
        full_message = expiration_message
        if upgrade_deadline and now < upgrade_deadline:
            full_message += upgrade_deadline_message
            using_upgrade_messaging = True
        else:
            using_upgrade_messaging = False

        language = get_language()
        date_string = get_date_string()
        formatted_expiration_date = date_string.format(
            language=language,
            formatted_date=expiration_date.strftime("%Y-%m-%d"),
            formatted_date_localized=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR)
        )
        if using_upgrade_messaging:
            formatted_upgrade_deadline = date_string.format(
                language=language,
                formatted_date=upgrade_deadline.strftime("%Y-%m-%d"),
                formatted_date_localized=strftime_localized(upgrade_deadline, EXPIRATION_DATE_FORMAT_STR)
            )

            return HTML(full_message).format(
                a_open=HTML(u'<a href="{upgrade_link}">').format(
                    upgrade_link=verified_upgrade_deadline_link(user=user, course=course)
                ),
                sronly_span_open=HTML('<span class="sr-only">'),
                span_close=HTML('</span>'),
                a_close=HTML('</a>'),
                expiration_date=HTML(formatted_expiration_date),
                strong_open=HTML('<strong>'),
                strong_close=HTML('</strong>'),
                line_break=HTML('<br>'),
                upgrade_deadline=HTML(formatted_upgrade_deadline)
            )

        else:
            return HTML(full_message).format(
                span_close=HTML('</span>'),
                expiration_date=HTML(formatted_expiration_date),
                strong_open=HTML('<strong>'),
                strong_close=HTML('</strong>'),
                line_break=HTML('<br>'),
            )
Exemple #20
0
    def get_group_for_user(cls, course_key, user, user_partition, **kwargs):  # pylint: disable=unused-argument
        """
        Returns the Group for the specified user.
        """

        # First, check if we have to deal with masquerading.
        # If the current user is masquerading as a specific student, use the
        # same logic as normal to return that student's group. If the current
        # user is masquerading as a generic student in a specific group, then
        # return that group.
        course_masquerade = get_course_masquerade(user, course_key)
        if course_masquerade and not is_masquerading_as_specific_student(user, course_key):
            masquerade_group = get_masquerading_user_group(course_key, user, user_partition)
            if masquerade_group is not None:
                return masquerade_group
            else:
                audit_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.AUDIT, {}).get('id')
                if course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID:
                    if course_masquerade.group_id != audit_mode_id:
                        return cls.FULL_ACCESS
                    else:
                        return cls.LIMITED_ACCESS

        # For now, treat everyone as a Full-access user, until we have the rest of the
        # feature gating logic in place.

        if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key):
            return cls.FULL_ACCESS

        # If CONTENT_TYPE_GATING is enabled use the following logic to determine whether a user should have FULL_ACCESS
        # or LIMITED_ACCESS

        course_mode = apps.get_model('course_modes.CourseMode')
        modes = course_mode.modes_for_course(course_key, include_expired=True, only_selectable=False)
        modes_dict = {mode.slug: mode for mode in modes}

        # If there is no verified mode, all users are granted FULL_ACCESS
        if not course_mode.has_verified_mode(modes_dict):
            return cls.FULL_ACCESS

        # If the user is a beta tester for this course they are granted FULL_ACCESS
        if CourseBetaTesterRole(course_key).has_user(user):
            return cls.FULL_ACCESS

        course_enrollment = apps.get_model('student.CourseEnrollment')

        mode_slug, is_active = course_enrollment.enrollment_mode_for_user(user, course_key)

        if mode_slug and is_active:
            course_mode = course_mode.mode_for_course(
                course_key,
                mode_slug,
                modes=modes,
            )
            if course_mode is None:
                LOG.error(
                    "User %s is in an unknown CourseMode '%s'"
                    " for course %s. Granting full access to content for this user",
                    user.username,
                    mode_slug,
                    course_key,
                )
                return cls.FULL_ACCESS

            if mode_slug == CourseMode.AUDIT:
                return cls.LIMITED_ACCESS
            else:
                return cls.FULL_ACCESS
        else:
            # Unenrolled users don't get gated content
            return cls.LIMITED_ACCESS
Exemple #21
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # if the user is has a role of staff, instructor or beta tester their access should not expire
        if user is None and enrollment is not None:
            user = enrollment.user

        if user:
            course_masquerade = get_course_masquerade(user, course_key)
            if course_masquerade:
                verified_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.VERIFIED, {}).get('id')
                is_verified = (course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID
                               and course_masquerade.group_id == verified_mode_id)
                is_full_access = (course_masquerade.user_partition_id == CONTENT_GATING_PARTITION_ID
                                  and course_masquerade.group_id == CONTENT_TYPE_GATE_GROUP_IDS['full_access'])
                is_staff = get_masquerade_role(user, course_key) == 'staff'
                if is_verified or is_full_access or is_staff:
                    return False
            else:
                staff_role = CourseStaffRole(course_key).has_user(user)
                instructor_role = CourseInstructorRole(course_key).has_user(user)
                beta_tester_role = CourseBetaTesterRole(course_key).has_user(user)

                if staff_role or instructor_role or beta_tester_role:
                    return False

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(user)
            is_in_holdback = False
            no_masquerade = get_course_masquerade(user, course_key) is None
            student_masquerade = is_masquerading_as_specific_student(user, course_key)
            if user and (no_masquerade or student_masquerade):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)