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

Sync opencraft-release/quince.1 with Upstream 20240122-1705882925 #619

Merged
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
12 changes: 10 additions & 2 deletions lms/djangoapps/courseware/block_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from edx_proctoring.api import get_attempt_status_summary
from edx_proctoring.services import ProctoringService
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.permissions import JwtRestrictedApplication
from edx_when.field_data import DateLookupFieldData
from eventtracking import tracker
from opaque_keys import InvalidKeyError
Expand Down Expand Up @@ -774,12 +775,19 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
)
else:
if user_auth_tuple is not None:
request.user, _ = user_auth_tuple
# When using JWT authentication, the second element contains the JWT token. We need it to determine
# whether the application that issued the token is restricted.
request.user, request.auth = user_auth_tuple
# This is verified by the `JwtRestrictedApplication` before it decodes the token.
request.successful_authenticator = authenticator
break

# NOTE (CCB): Allow anonymous GET calls (e.g. for transcripts). Modifying this view is simpler than updating
# the XBlocks to use `handle_xblock_callback_noauth`, which is practically identical to this view.
if request.method != 'GET' and not (request.user and request.user.is_authenticated):
# Block all request types coming from restricted applications.
if (
request.method != 'GET' and not (request.user and request.user.is_authenticated)
) or JwtRestrictedApplication().has_permission(request, None): # type: ignore
return HttpResponseForbidden('Unauthenticated')

request.user.known = request.user.is_authenticated
Expand Down
6 changes: 3 additions & 3 deletions lms/djangoapps/courseware/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ def _ora_assessment_to_assignment(
date_config_type = block_data.get_xblock_field(ora_block, 'date_config_type', 'manual')
assignment_type = block_data.get_xblock_field(ora_block, 'format', None)
block_title = block_data.get_xblock_field(ora_block, 'title', _('Open Response Assessment'))
course_key = block_data.root_block_usage_key
block_key = block_data.root_block_usage_key

# Steps with no "due" date, like staff or training, should not show up here
assessment_step_due = assessment.get('start')
Expand All @@ -712,7 +712,7 @@ def _ora_assessment_to_assignment(
extra_info = None
elif date_config_type == 'course_end':
assessment_start = None
assessment_due = block_data.get_xblock_field(course_key, 'end')
assessment_due = block_data.get_xblock_field(block_key, 'end')
extra_info = None
else:
assessment_start, assessment_due = None, None
Expand Down Expand Up @@ -746,7 +746,7 @@ def _ora_assessment_to_assignment(
now = datetime.now(pytz.UTC)
assignment_released = not assessment_start or assessment_start < now
if assignment_released:
url = reverse('jump_to', args=[course_key, ora_block])
url = reverse('jump_to', args=[block_key.course_key, ora_block])

past_due = not complete and assessment_due and assessment_due < now
first_component_block_id = str(ora_block)
Expand Down
22 changes: 21 additions & 1 deletion lms/djangoapps/courseware/tests/test_block_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from openedx.core.djangoapps.credit.api import set_credit_requirement_status, set_credit_requirements
from openedx.core.djangoapps.credit.models import CreditCourse
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user
from openedx.core.djangoapps.oauth_dispatch.jwt import _create_jwt, create_jwt_for_user
from openedx.core.djangoapps.oauth_dispatch.tests.factories import AccessTokenFactory, ApplicationFactory
from openedx.core.lib.courses import course_image_url
from openedx.core.lib.gating import api as gating_api
Expand Down Expand Up @@ -383,6 +383,26 @@ def test_jwt_authentication(self):
response = self.client.post(dispatch_url, {}, **headers)
assert 200 == response.status_code

def test_jwt_authentication_with_restricted_application(self):
"""Test that the XBlock endpoint disallows JWT authentication with restricted applications."""

def _mock_create_restricted_jwt(*args, **kwargs):
"""Pass an additional argument to `_create_jwt` without modifying the signature of `create_jwt_for_user`."""
kwargs['is_restricted'] = True
return _create_jwt(*args, **kwargs)

with patch('openedx.core.djangoapps.oauth_dispatch.jwt._create_jwt', _mock_create_restricted_jwt):
token = create_jwt_for_user(self.mock_user)

dispatch_url = self._get_dispatch_url()
headers = {'HTTP_AUTHORIZATION': 'JWT ' + token}

response = self.client.get(dispatch_url, {}, **headers)
assert 403 == response.status_code

response = self.client.post(dispatch_url, {}, **headers)
assert 403 == response.status_code

def test_missing_position_handler(self):
"""
Test that sending POST request without or invalid position argument don't raise server error
Expand Down
2 changes: 1 addition & 1 deletion openedx/features/survey_report/tests/test_query_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_get_unique_courses_offered(self):
"""
Test that get_unique_courses_offered returns the correct number of courses.
"""
course_overview = CourseOverviewFactory.create(id=self.first_course.id, start="2019-01-01", end="2024-01-01")
course_overview = CourseOverviewFactory.create(id=self.first_course.id, start="2019-01-01", end="9999-01-01")
CourseEnrollmentFactory.create(user=self.user, course_id=course_overview.id)
CourseEnrollmentFactory.create(user=self.user1, course_id=course_overview.id)
CourseEnrollmentFactory.create(user=self.user2, course_id=course_overview.id)
Expand Down
Loading