Commit 3ddf8263 by Waheed Ahmed

Merge pull request #4792 from edx/waheed/lms11217-fix-500-error-in-views-with-incompete-course-id

Fixed 500 error on views with incomplete course id in url.
parents ea967148 e26efea2
...@@ -178,11 +178,10 @@ class TestLTIModuleListing(ModuleStoreTestCase): ...@@ -178,11 +178,10 @@ class TestLTIModuleListing(ModuleStoreTestCase):
def test_lti_rest_bad_course(self): def test_lti_rest_bad_course(self):
"""Tests what happens when the lti listing rest endpoint gets a bad course_id""" """Tests what happens when the lti listing rest endpoint gets a bad course_id"""
bad_ids = [u"sf", u"dne/dne/dne", u"fo/ey/\u5305"] bad_ids = [u"sf", u"dne/dne/dne", u"fo/ey/\\u5305"]
request = mock.Mock()
request.method = 'GET'
for bad_course_id in bad_ids: for bad_course_id in bad_ids:
response = get_course_lti_endpoints(request, bad_course_id) lti_rest_endpoints_url = 'courses/{}/lti_rest_endpoints/'.format(bad_course_id)
response = self.client.get(lti_rest_endpoints_url)
self.assertEqual(404, response.status_code) self.assertEqual(404, response.status_code)
def test_lti_rest_listing(self): def test_lti_rest_listing(self):
......
...@@ -5,7 +5,7 @@ Tests courseware views.py ...@@ -5,7 +5,7 @@ Tests courseware views.py
import unittest import unittest
from datetime import datetime from datetime import datetime
from mock import MagicMock, patch from mock import MagicMock, patch, create_autospec
from pytz import UTC from pytz import UTC
from django.test import TestCase from django.test import TestCase
...@@ -152,6 +152,10 @@ class ViewsTestCase(TestCase): ...@@ -152,6 +152,10 @@ class ViewsTestCase(TestCase):
response = self.client.get('/courses/MITx/3.091X/') response = self.client.get('/courses/MITx/3.091X/')
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_incomplete_course_id(self):
response = self.client.get('/courses/MITx/')
self.assertEqual(response.status_code, 404)
def test_index_invalid_position(self): def test_index_invalid_position(self):
request_url = '/'.join([ request_url = '/'.join([
'/courses', '/courses',
...@@ -562,3 +566,26 @@ class ProgressPageTests(ModuleStoreTestCase): ...@@ -562,3 +566,26 @@ class ProgressPageTests(ModuleStoreTestCase):
def test_non_asci_grade_cutoffs(self): def test_non_asci_grade_cutoffs(self):
resp = views.progress(self.request, self.course.id.to_deprecated_string()) resp = views.progress(self.request, self.course.id.to_deprecated_string())
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
class TestVerifyCourseIdDecorator(TestCase):
"""
Tests for the verify_course_id decorator.
"""
def setUp(self):
self.request = RequestFactory().get("foo")
self.valid_course_id = "edX/test/1"
self.invalid_course_id = "edX/"
def test_decorator_with_valid_course_id(self):
mocked_view = create_autospec(views.course_about)
view_function = views.verify_course_id(mocked_view)
view_function(self.request, self.valid_course_id)
self.assertTrue(mocked_view.called)
def test_decorator_with_invalid_course_id(self):
mocked_view = create_autospec(views.course_about)
view_function = views.verify_course_id(mocked_view)
self.assertRaises(Http404, view_function, self.request, self.invalid_course_id)
self.assertFalse(mocked_view.called)
...@@ -23,6 +23,7 @@ from edxmako.shortcuts import render_to_response, render_to_string ...@@ -23,6 +23,7 @@ from edxmako.shortcuts import render_to_response, render_to_string
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.db import transaction from django.db import transaction
from functools import wraps
from markupsafe import escape from markupsafe import escape
from courseware import grades from courseware import grades
...@@ -81,6 +82,24 @@ def user_groups(user): ...@@ -81,6 +82,24 @@ def user_groups(user):
return group_names return group_names
def verify_course_id(view_func):
"""
This decorator should only be used with views whose second argument is course_id.
If course_id is not valid raise 404.
"""
@wraps(view_func)
def _decorated(request, course_id, *args, **kwargs):
try:
SlashSeparatedCourseKey.from_deprecated_string(course_id)
except InvalidKeyError:
raise Http404
response = view_func(request, course_id, *args, **kwargs)
return response
return _decorated
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_if_anonymous @cache_if_anonymous
def courses(request): def courses(request):
...@@ -242,6 +261,7 @@ def chat_settings(course, user): ...@@ -242,6 +261,7 @@ def chat_settings(course, user):
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
@verify_course_id
def index(request, course_id, chapter=None, section=None, def index(request, course_id, chapter=None, section=None,
position=None): position=None):
""" """
...@@ -266,7 +286,9 @@ def index(request, course_id, chapter=None, section=None, ...@@ -266,7 +286,9 @@ def index(request, course_id, chapter=None, section=None,
- HTTPresponse - HTTPresponse
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
user = User.objects.prefetch_related("groups").get(id=request.user.id) user = User.objects.prefetch_related("groups").get(id=request.user.id)
request.user = user # keep just one instance of User request.user = user # keep just one instance of User
course = get_course_with_access(user, 'load', course_key, depth=2) course = get_course_with_access(user, 'load', course_key, depth=2)
...@@ -458,6 +480,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -458,6 +480,7 @@ def index(request, course_id, chapter=None, section=None,
@ensure_csrf_cookie @ensure_csrf_cookie
@verify_course_id
def jump_to_id(request, course_id, module_id): def jump_to_id(request, course_id, module_id):
""" """
This entry point allows for a shorter version of a jump to where just the id of the element is This entry point allows for a shorter version of a jump to where just the id of the element is
...@@ -516,13 +539,16 @@ def jump_to(request, course_id, location): ...@@ -516,13 +539,16 @@ def jump_to(request, course_id, location):
@ensure_csrf_cookie @ensure_csrf_cookie
@verify_course_id
def course_info(request, course_id): def course_info(request, course_id):
""" """
Display the course's info.html, or 404 if there is no such course. Display the course's info.html, or 404 if there is no such course.
Assumes the course_id is in a valid format. Assumes the course_id is in a valid format.
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key) course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course) staff_access = has_access(request.user, 'staff', course)
masq = setup_masquerade(request, staff_access) # allow staff to toggle masquerade on info page masq = setup_masquerade(request, staff_access) # allow staff to toggle masquerade on info page
...@@ -544,16 +570,15 @@ def course_info(request, course_id): ...@@ -544,16 +570,15 @@ def course_info(request, course_id):
@ensure_csrf_cookie @ensure_csrf_cookie
@verify_course_id
def static_tab(request, course_id, tab_slug): def static_tab(request, course_id, tab_slug):
""" """
Display the courses tab with the given name. Display the courses tab with the given name.
Assumes the course_id is in a valid format. Assumes the course_id is in a valid format.
""" """
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except InvalidKeyError:
raise Http404
course = get_course_with_access(request.user, 'load', course_key) course = get_course_with_access(request.user, 'load', course_key)
...@@ -579,13 +604,16 @@ def static_tab(request, course_id, tab_slug): ...@@ -579,13 +604,16 @@ def static_tab(request, course_id, tab_slug):
@ensure_csrf_cookie @ensure_csrf_cookie
@verify_course_id
def syllabus(request, course_id): def syllabus(request, course_id):
""" """
Display the course's syllabus.html, or 404 if there is no such course. Display the course's syllabus.html, or 404 if there is no such course.
Assumes the course_id is in a valid format. Assumes the course_id is in a valid format.
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key) course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course) staff_access = has_access(request.user, 'staff', course)
...@@ -621,7 +649,9 @@ def course_about(request, course_id): ...@@ -621,7 +649,9 @@ def course_about(request, course_id):
settings.FEATURES.get('ENABLE_MKTG_SITE', False) settings.FEATURES.get('ENABLE_MKTG_SITE', False)
): ):
raise Http404 raise Http404
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'see_exists', course_key) course = get_course_with_access(request.user, 'see_exists', course_key)
registered = registered_for_course(course, request.user) registered = registered_for_course(course, request.user)
staff_access = has_access(request.user, 'staff', course) staff_access = has_access(request.user, 'staff', course)
...@@ -683,12 +713,14 @@ def course_about(request, course_id): ...@@ -683,12 +713,14 @@ def course_about(request, course_id):
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_if_anonymous @cache_if_anonymous
@verify_course_id
def mktg_course_about(request, course_id): def mktg_course_about(request, course_id):
""" """
This is the button that gets put into an iframe on the Drupal site This is the button that gets put into an iframe on the Drupal site
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
try: try:
course = get_course_with_access(request.user, 'see_exists', course_key) course = get_course_with_access(request.user, 'see_exists', course_key)
except (ValueError, Http404) as e: except (ValueError, Http404) as e:
...@@ -739,13 +771,17 @@ def mktg_course_about(request, course_id): ...@@ -739,13 +771,17 @@ def mktg_course_about(request, course_id):
@login_required @login_required
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
@transaction.commit_manually @transaction.commit_manually
@verify_course_id
def progress(request, course_id, student_id=None): def progress(request, course_id, student_id=None):
""" """
Wraps "_progress" with the manual_transaction context manager just in case Wraps "_progress" with the manual_transaction context manager just in case
there are unanticipated errors. there are unanticipated errors.
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
with grades.manual_transaction(): with grades.manual_transaction():
return _progress(request, SlashSeparatedCourseKey.from_deprecated_string(course_id), student_id) return _progress(request, course_key, student_id)
def _progress(request, course_key, student_id): def _progress(request, course_key, student_id):
...@@ -816,16 +852,15 @@ def fetch_reverify_banner_info(request, course_key): ...@@ -816,16 +852,15 @@ def fetch_reverify_banner_info(request, course_key):
@login_required @login_required
@verify_course_id
def submission_history(request, course_id, student_username, location): def submission_history(request, course_id, student_username, location):
"""Render an HTML fragment (meant for inclusion elsewhere) that renders a """Render an HTML fragment (meant for inclusion elsewhere) that renders a
history of all state changes made by this user for this problem location. history of all state changes made by this user for this problem location.
Right now this only works for problems because that's all Right now this only works for problems because that's all
StudentModuleHistory records. StudentModuleHistory records.
""" """
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except (InvalidKeyError, AssertionError):
return HttpResponse(escape(_(u'Invalid course id.')))
try: try:
usage_key = course_key.make_usage_key_from_deprecated_string(location) usage_key = course_key.make_usage_key_from_deprecated_string(location)
...@@ -925,6 +960,7 @@ def get_static_tab_contents(request, course, tab): ...@@ -925,6 +960,7 @@ def get_static_tab_contents(request, course, tab):
@require_GET @require_GET
@verify_course_id
def get_course_lti_endpoints(request, course_id): def get_course_lti_endpoints(request, course_id):
""" """
View that, given a course_id, returns the a JSON object that enumerates all of the LTI endpoints for that course. View that, given a course_id, returns the a JSON object that enumerates all of the LTI endpoints for that course.
...@@ -941,10 +977,8 @@ def get_course_lti_endpoints(request, course_id): ...@@ -941,10 +977,8 @@ def get_course_lti_endpoints(request, course_id):
Returns: Returns:
(django response object): HTTP response. 404 if course is not found, otherwise 200 with JSON body. (django response object): HTTP response. 404 if course is not found, otherwise 200 with JSON body.
""" """
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except InvalidKeyError:
return HttpResponse(status=404)
try: try:
course = get_course(course_key, depth=2) course = get_course(course_key, depth=2)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment