Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: broken completion api and return authentication error when xblock is not public #35554

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions lms/djangoapps/course_home_api/outline/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,29 @@
from django.urls import reverse # lint-amnesty, pylint: disable=wrong-import-order
from django.utils.translation import gettext as _ # lint-amnesty, pylint: disable=wrong-import-order
from edx_django_utils import monitoring as monitoring_utils # lint-amnesty, pylint: disable=wrong-import-order
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication # lint-amnesty, pylint: disable=wrong-import-order
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser # lint-amnesty, pylint: disable=wrong-import-order
from edx_rest_framework_extensions.auth.jwt.authentication import \
JwtAuthentication # lint-amnesty, pylint: disable=wrong-import-order
from edx_rest_framework_extensions.auth.session.authentication import \
SessionAuthenticationAllowInactiveUser # lint-amnesty, pylint: disable=wrong-import-order
from opaque_keys.edx.keys import CourseKey # lint-amnesty, pylint: disable=wrong-import-order
from rest_framework.decorators import api_view, authentication_classes, permission_classes # lint-amnesty, pylint: disable=wrong-import-order
from rest_framework.decorators import ( # lint-amnesty, pylint: disable=wrong-import-order
api_view,
authentication_classes,
permission_classes
)
from rest_framework.exceptions import APIException, ParseError # lint-amnesty, pylint: disable=wrong-import-order
from rest_framework.generics import RetrieveAPIView # lint-amnesty, pylint: disable=wrong-import-order
from rest_framework.permissions import IsAuthenticated # lint-amnesty, pylint: disable=wrong-import-order
from rest_framework.response import Response # lint-amnesty, pylint: disable=wrong-import-order
from xblock.completable import XBlockCompletionMode
from xblock.core import XBlock

from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.views import expose_header
from lms.djangoapps.course_goals.api import (
add_course_goal,
get_course_goal,
)
from lms.djangoapps.course_goals.api import add_course_goal, get_course_goal
from lms.djangoapps.course_goals.models import CourseGoal
from lms.djangoapps.course_home_api.outline.serializers import (
CourseBlockSerializer,
OutlineTabSerializer,
)
from lms.djangoapps.course_home_api.outline.serializers import CourseBlockSerializer, OutlineTabSerializer
from lms.djangoapps.course_home_api.utils import get_course_or_403
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs
Expand All @@ -44,8 +46,8 @@
from lms.djangoapps.courseware.views.views import get_cert_data
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.utils import OptimizelyClient
from openedx.core.djangoapps.content.learning_sequences.api import get_user_course_outline
from openedx.core.djangoapps.content.course_overviews.api import get_course_overview_or_404
from openedx.core.djangoapps.content.learning_sequences.api import get_user_course_outline
from openedx.core.djangoapps.course_groups.cohorts import get_cohort
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.features.course_duration_limits.access import get_access_expiration_data
Expand All @@ -58,9 +60,10 @@
from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url
from openedx.features.course_experience.utils import get_course_outline_block_tree, get_start_block
from openedx.features.discounts.utils import generate_offer_data
from xblock.core import XBlock
from xblock.completable import XBlockCompletionMode
from xmodule.course_block import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.course_block import ( # lint-amnesty, pylint: disable=wrong-import-order
COURSE_VISIBILITY_PUBLIC,
COURSE_VISIBILITY_PUBLIC_OUTLINE
)


class UnableToDismissWelcomeMessage(APIException):
Expand Down Expand Up @@ -599,10 +602,13 @@ def completions_dict(self):
"""
course_key_string = self.kwargs.get('course_key_string')
course_key = CourseKey.from_string(course_key_string)
completions = BlockCompletion.objects.filter(user=self.request.user, context_key=course_key).values_list(
'block_key',
'completion',
)
if self.request.user.is_anonymous:
completions = BlockCompletion.objects.none()
else:
completions = BlockCompletion.objects.filter(user=self.request.user, context_key=course_key).values_list(
'block_key',
'completion',
)
return {
str(block_key): completion
for block_key, completion in completions
Expand Down
56 changes: 28 additions & 28 deletions lms/djangoapps/courseware/block_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
"""

from __future__ import annotations

import json
import logging
import textwrap
from collections import OrderedDict

from functools import partial
from typing import TYPE_CHECKING, Callable

from completion.services import CompletionService
from django.conf import settings
Expand All @@ -34,39 +35,36 @@
from opaque_keys.edx.keys import CourseKey, UsageKey
from rest_framework.decorators import api_view
from rest_framework.exceptions import APIException
from typing import Callable, TYPE_CHECKING
from web_fragments.fragment import Fragment
from xblock.django.request import django_to_webob_request, webob_to_django_response
from xblock.exceptions import NoSuchHandlerError, NoSuchViewError
from xblock.reference.plugins import FSService
from xblock.runtime import KvsFieldData

from lms.djangoapps.teams.services import TeamsService
from openedx.core.lib.xblock_services.call_to_action import CallToActionService
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.library_tools import LibraryToolsService
from xmodule.modulestore.django import XBlockI18nService, modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.partitions.partitions_service import PartitionService
from xmodule.util.sandboxing import SandboxService
from xmodule.services import EventPublishingService, RebindUserService, SettingsService, TeamsConfigurationService
from common.djangoapps.edxmako.services import MakoService
from common.djangoapps.static_replace.services import ReplaceURLService
from common.djangoapps.static_replace.wrapper import replace_urls_wrapper
from common.djangoapps.student.models import anonymous_id_for_user
from common.djangoapps.student.roles import CourseBetaTesterRole
from common.djangoapps.util import milestones_helpers
from common.djangoapps.util.json_request import JsonResponse
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
from lms.djangoapps.courseware.access import get_user_role, has_access
from lms.djangoapps.courseware.entrance_exams import user_can_skip_entrance_exam, user_has_passed_entrance_exam
from lms.djangoapps.courseware.exceptions import XblockAccessDenied
from lms.djangoapps.courseware.field_overrides import OverrideFieldData
from lms.djangoapps.courseware.masquerade import (
MasqueradingKeyValueStore,
filter_displayed_blocks,
is_masquerading_as_specific_student,
setup_masquerade
)
from lms.djangoapps.courseware.model_data import DjangoKeyValueStore, FieldDataCache
from lms.djangoapps.courseware.field_overrides import OverrideFieldData
from lms.djangoapps.courseware.services import UserStateService
from lms.djangoapps.grades.api import GradesUtilService
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from lms.djangoapps.lms_xblock.runtime import UserTagsService, lms_wrappers_aside, lms_applicable_aside_types
from lms.djangoapps.lms_xblock.runtime import UserTagsService, lms_applicable_aside_types, lms_wrappers_aside
from lms.djangoapps.teams.services import TeamsService
from lms.djangoapps.verify_student.services import XBlockVerificationService
from openedx.core.djangoapps.bookmarks.api import BookmarksService
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
Expand All @@ -76,27 +74,25 @@
from openedx.core.djangolib.markup import HTML
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.core.lib.api.view_utils import view_auth_classes
from openedx.core.lib.cache_utils import CacheService
from openedx.core.lib.gating.services import GatingService
from openedx.core.lib.license import wrap_with_license
from openedx.core.lib.url_utils import quote_slashes, unquote_slashes
from openedx.core.lib.xblock_utils import (
add_staff_markup,
get_aside_from_xblock,
hash_resource,
is_xblock_aside
)
from openedx.core.lib.xblock_services.call_to_action import CallToActionService
from openedx.core.lib.xblock_utils import add_staff_markup, get_aside_from_xblock, hash_resource, is_xblock_aside
from openedx.core.lib.xblock_utils import request_token as xblock_request_token
from openedx.core.lib.xblock_utils import wrap_xblock
from openedx.features.content_type_gating.services import ContentTypeGatingService
from openedx.features.course_duration_limits.access import course_expiration_wrapper
from openedx.features.discounts.utils import offer_banner_wrapper
from openedx.features.content_type_gating.services import ContentTypeGatingService
from common.djangoapps.student.models import anonymous_id_for_user
from common.djangoapps.student.roles import CourseBetaTesterRole
from common.djangoapps.util import milestones_helpers
from common.djangoapps.util.json_request import JsonResponse
from common.djangoapps.edxmako.services import MakoService
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
from openedx.core.lib.cache_utils import CacheService
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.library_tools import LibraryToolsService
from xmodule.modulestore.django import XBlockI18nService, modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.partitions.partitions_service import PartitionService
from xmodule.services import EventPublishingService, RebindUserService, SettingsService, TeamsConfigurationService
from xmodule.util.sandboxing import SandboxService

if TYPE_CHECKING:
from rest_framework.request import Request
Expand Down Expand Up @@ -390,6 +386,7 @@ def get_block_for_descriptor(
student_data: KvsFieldData | None = None,
request_token: str | None = None,
user_location: str | None = None,
raise_error: bool = False,
) -> XBlock | None:
"""
Implements get_block, extracting out the request-specific functionality.
Expand Down Expand Up @@ -479,6 +476,8 @@ def get_block_for_descriptor(
if access or caller_will_handle_access_error:
block.has_access_error = bool(caller_will_handle_access_error)
return block
if raise_error:
raise XblockAccessDenied()
return None
return block

Expand Down Expand Up @@ -875,6 +874,7 @@ def get_block_by_usage_id(request, course_id, usage_id, disable_staff_debug_info
disable_staff_debug_info=disable_staff_debug_info,
course=course,
will_recheck_access=will_recheck_access,
raise_error=True,
)
if instance is None:
# Either permissions just changed, or someone is trying to be clever
Expand Down
13 changes: 13 additions & 0 deletions lms/djangoapps/courseware/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Redirect(Exception):
"""
Exception class that requires redirecting to a URL.
"""

def __init__(self, url):
super().__init__()
self.url = url
Expand Down Expand Up @@ -41,3 +42,15 @@ def __init__(self, course_key):
course run key or stringified version thereof.
"""
super().__init__(f"Course run not found: {course_key}")


class XblockAccessDenied(ValueError):
"""
Indicate that a XBlock is not publicly accessible.
"""

def __init__(self):
"""
Initialize XblockAccessDenied exception.
"""
super().__init__("Please signin to view this xblock!")
6 changes: 3 additions & 3 deletions lms/djangoapps/courseware/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def test_success_enrolled_student(self):
def test_unauthenticated(self):
self.setup_course()
self.setup_user(admin=False, enroll=True, login=False)
self.verify_response(expected_response_code=404)
self.verify_response(expected_response_code=403)

def test_unenrolled_student(self):
self.setup_course()
Expand All @@ -236,14 +236,14 @@ def test_fail_block_unreleased(self):
self.setup_user(admin=False, enroll=True, login=True)
self.block_to_be_tested.start = datetime.max
modulestore().update_item(self.block_to_be_tested, self.user.id)
self.verify_response(expected_response_code=404)
self.verify_response(expected_response_code=403)

def test_fail_block_nonvisible(self):
self.setup_course()
self.setup_user(admin=False, enroll=True, login=True)
self.block_to_be_tested.visible_to_staff_only = True
modulestore().update_item(self.block_to_be_tested, self.user.id)
self.verify_response(expected_response_code=404)
self.verify_response(expected_response_code=403)

@ddt.data(
'vertical_block',
Expand Down
50 changes: 25 additions & 25 deletions lms/djangoapps/courseware/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q, prefetch_related_objects
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, JsonResponse
from django.shortcuts import redirect
from django.http import JsonResponse, Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.template.context_processors import csrf
from django.urls import reverse
from django.utils.decorators import method_decorator
Expand All @@ -33,30 +33,24 @@
from django.views.generic import View
from edx_django_utils.monitoring import set_custom_attribute, set_custom_attributes_for_course_key
from ipware.ip import get_client_ip
from lms.djangoapps.static_template_view.views import render_500
from markupsafe import escape
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
from openedx_filters.learning.filters import CourseAboutRenderStarted, RenderXBlockStarted
from requests.exceptions import ConnectionError, Timeout # pylint: disable=redefined-builtin
from pytz import UTC
from requests.exceptions import ConnectionError, Timeout # pylint: disable=redefined-builtin
from rest_framework import status
from rest_framework.decorators import api_view, throttle_classes
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
from token_utils.api import unpack_token_for
from web_fragments.fragment import Fragment
from xmodule.course_block import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from xmodule.tabs import CourseTabList
from xmodule.x_module import STUDENT_VIEW

from common.djangoapps.course_modes.models import CourseMode, get_course_prices
from common.djangoapps.edxmako.shortcuts import marketing_link, render_to_response, render_to_string
from common.djangoapps.student import auth
from common.djangoapps.student.roles import CourseStaffRole
from common.djangoapps.student.models import CourseEnrollment, UserTestGroup
from common.djangoapps.student.roles import CourseStaffRole
from common.djangoapps.util.cache import cache, cache_if_anonymous
from common.djangoapps.util.course import course_location_from_key
from common.djangoapps.util.db import outer_atomic
Expand Down Expand Up @@ -84,16 +78,16 @@
sort_by_start_date
)
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect, XblockAccessDenied
from lms.djangoapps.courseware.masquerade import is_masquerading_as_specific_student, setup_masquerade
from lms.djangoapps.courseware.model_data import FieldDataCache
from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentModule
from lms.djangoapps.courseware.permissions import MASQUERADE_AS_STUDENT, VIEW_COURSE_HOME, VIEW_COURSEWARE
from lms.djangoapps.courseware.toggles import (
course_is_invitation_only,
courseware_mfe_search_is_enabled,
COURSEWARE_MICROFRONTEND_ENABLE_NAVIGATION_SIDEBAR,
COURSEWARE_MICROFRONTEND_ALWAYS_OPEN_AUXILIARY_SIDEBAR,
COURSEWARE_MICROFRONTEND_ENABLE_NAVIGATION_SIDEBAR,
course_is_invitation_only,
courseware_mfe_search_is_enabled
)
from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient
from lms.djangoapps.courseware.utils import (
Expand All @@ -106,6 +100,7 @@
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.instructor.enrollment import uses_shib
from lms.djangoapps.instructor.views.api import require_global_staff
from lms.djangoapps.static_template_view.views import render_500
from lms.djangoapps.survey import views as survey_views
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.catalog.utils import (
Expand Down Expand Up @@ -142,13 +137,15 @@
from openedx.features.course_experience.utils import dates_banner_should_display
from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML
from openedx.features.enterprise_support.api import data_sharing_consent_required
from xmodule.course_block import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from xmodule.tabs import CourseTabList
from xmodule.x_module import STUDENT_VIEW

from ..block_render import get_block, get_block_by_usage_id, get_block_for_descriptor
from ..tabs import _get_dynamic_tabs
from ..toggles import (
COURSEWARE_OPTIMIZED_RENDER_XBLOCK,
ENABLE_COURSE_DISCOVERY_DEFAULT_LANGUAGE_FILTER,
)
from ..toggles import COURSEWARE_OPTIMIZED_RENDER_XBLOCK, ENABLE_COURSE_DISCOVERY_DEFAULT_LANGUAGE_FILTER

log = logging.getLogger("edx.courseware")

Expand Down Expand Up @@ -1586,14 +1583,17 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True, disable_sta

# get the block, which verifies whether the user has access to the block.
recheck_access = request.GET.get('recheck_access') == '1'
block, _ = get_block_by_usage_id(
request,
str(course_key),
str(usage_key),
disable_staff_debug_info=disable_staff_debug_info,
course=course,
will_recheck_access=recheck_access,
)
try:
block, _ = get_block_by_usage_id(
request,
str(course_key),
str(usage_key),
disable_staff_debug_info=disable_staff_debug_info,
course=course,
will_recheck_access=recheck_access,
)
except XblockAccessDenied as ex:
return HttpResponseForbidden(str(ex))

student_view_context = request.GET.dict()
student_view_context['show_bookmark_button'] = request.GET.get('show_bookmark_button', '0') == '1'
Expand Down
Loading
Loading