Add middleware optionally to catch unenrolled students who fail has_access (TNL-286)

This commit is contained in:
Adam Palay
2014-09-16 10:58:30 -04:00
parent 6bf5207494
commit 7f4da14185
6 changed files with 117 additions and 2 deletions

View File

@@ -20,6 +20,7 @@ from xmodule.x_module import STUDENT_VIEW
from courseware.access import has_access
from courseware.model_data import FieldDataCache
from courseware.module_render import get_module
from student.models import CourseEnrollment
import branding
log = logging.getLogger(__name__)
@@ -72,7 +73,13 @@ def get_course_by_id(course_key, depth=0):
raise Http404("Course not found.")
def get_course_with_access(user, action, course_key, depth=0):
class UserNotEnrolled(Http404):
def __init__(self, course_key):
super(UserNotEnrolled, self).__init__()
self.course_key = course_key
def get_course_with_access(user, action, course_key, depth=0, check_if_enrolled=False):
"""
Given a course_key, look up the corresponding course descriptor,
check that the user has the access to perform the specified action
@@ -86,6 +93,11 @@ def get_course_with_access(user, action, course_key, depth=0):
course = get_course_by_id(course_key, depth=depth)
if not has_access(user, action, course, course_key):
if check_if_enrolled and not CourseEnrollment.is_enrolled(user, course_key):
# If user is not enrolled, raise UserNotEnrolled exception that will
# be caught by middleware
raise UserNotEnrolled(course_key)
# Deliberately return a non-specific error message to avoid
# leaking info about access control settings
raise Http404("Course not found.")

View File

@@ -0,0 +1,23 @@
"""
Middleware for the courseware app
"""
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from courseware.courses import UserNotEnrolled
class RedirectUnenrolledMiddleware(object):
"""
Catch UserNotEnrolled errors thrown by `get_course_with_access` and redirect
users to the course about page
"""
def process_exception(self, request, exception):
if isinstance(exception, UserNotEnrolled):
course_key = exception.course_key
return redirect(
reverse(
'courseware.views.course_about',
args=[course_key.to_deprecated_string()]
)
)

View File

@@ -0,0 +1,53 @@
"""
Tests for courseware middleware
"""
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from django.test.client import RequestFactory
from django.http import Http404
from mock import patch
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
import courseware.courses as courses
from courseware.middleware import RedirectUnenrolledMiddleware
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class CoursewareMiddlewareTestCase(ModuleStoreTestCase):
"""Tests that courseware middleware is correctly redirected"""
def setUp(self):
self.course = CourseFactory.create()
def check_user_not_enrolled_redirect(self):
"""A UserNotEnrolled exception should trigger a redirect"""
request = RequestFactory().get("dummy_url")
response = RedirectUnenrolledMiddleware().process_exception(
request, courses.UserNotEnrolled(self.course.id)
)
self.assertEqual(response.status_code, 302)
# make sure we redirect to the course about page
expected_url = reverse(
"about_course", args=[self.course.id.to_deprecated_string()]
)
target_url = response._headers['location'][1]
self.assertTrue(target_url.endswith(expected_url))
def test_user_not_enrolled_redirect(self):
self.check_user_not_enrolled_redirect()
@patch.dict("django.conf.settings.FEATURES", {"ENABLE_MKTG_SITE": True})
def test_user_not_enrolled_redirect_mktg(self):
self.check_user_not_enrolled_redirect()
def test_process_404(self):
"""A 404 should not trigger anything"""
request = RequestFactory().get("dummy_url")
response = RedirectUnenrolledMiddleware().process_exception(
request, Http404()
)
self.assertIsNone(response)