Commit 38916f85 by chrisndodge

Merge pull request #2713 from edx/cdodge/add-auth-to-wiki

add some authorization checks to the Wiki
parents a8a20a6a 10465e3e
...@@ -399,6 +399,10 @@ class CourseFields(object): ...@@ -399,6 +399,10 @@ class CourseFields(object):
max_student_enrollments_allowed = Integer(help="Limit the number of students allowed to enroll in this course.", max_student_enrollments_allowed = Integer(help="Limit the number of students allowed to enroll in this course.",
scope=Scope.settings) scope=Scope.settings)
allow_public_wiki_access = Boolean(help="Whether to allow an unenrolled user to view the Wiki",
default=False,
scope=Scope.settings)
class CourseDescriptor(CourseFields, SequenceDescriptor): class CourseDescriptor(CourseFields, SequenceDescriptor):
module_class = SequenceModule module_class = SequenceModule
......
...@@ -3,14 +3,19 @@ from urlparse import urlparse ...@@ -3,14 +3,19 @@ from urlparse import urlparse
from django.http import Http404 from django.http import Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.conf import settings
from django.core.exceptions import PermissionDenied
from wiki.models import reverse as wiki_reverse from wiki.models import reverse as wiki_reverse
from courseware.access import has_access from courseware.access import has_access
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from student.models import CourseEnrollment
IN_COURSE_WIKI_REGEX = r'/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/wiki/(?P<wiki_path>.*|)$' IN_COURSE_WIKI_REGEX = r'/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/wiki/(?P<wiki_path>.*|)$'
IN_COURSE_WIKI_COMPILED_REGEX = re.compile(IN_COURSE_WIKI_REGEX)
WIKI_ROOT_ACCESS_COMPILED_REGEX = re.compile(r'^/wiki/(?P<wiki_path>.*|)$')
class Middleware(object): class Middleware(object):
""" """
...@@ -44,6 +49,11 @@ class Middleware(object): ...@@ -44,6 +49,11 @@ class Middleware(object):
referer = request.META.get('HTTP_REFERER') referer = request.META.get('HTTP_REFERER')
destination = request.path destination = request.path
# Check to see if we don't allow top-level access to the wiki via the /wiki/xxxx/yyy/zzz URLs
# this will help prevent people from writing pell-mell to the Wiki in an unstructured way
path_match = WIKI_ROOT_ACCESS_COMPILED_REGEX.match(destination)
if path_match and not settings.FEATURES.get('ALLOW_WIKI_ROOT_ACCESS', False):
raise PermissionDenied()
if request.method == 'GET': if request.method == 'GET':
new_destination = self.get_redirected_url(request.user, referer, destination) new_destination = self.get_redirected_url(request.user, referer, destination)
...@@ -53,10 +63,20 @@ class Middleware(object): ...@@ -53,10 +63,20 @@ class Middleware(object):
self.redirected = True self.redirected = True
return redirect(new_destination) return redirect(new_destination)
course_match = re.match(IN_COURSE_WIKI_REGEX, destination) course_match = IN_COURSE_WIKI_COMPILED_REGEX.match(destination)
if course_match: if course_match:
course_id = course_match.group('course_id') course_id = course_match.group('course_id')
prepend_string = '/courses/' + course_match.group('course_id')
# Authorization Check
# Let's see if user is enrolled or the course allows for public access
course = get_course_with_access(request.user, course_id, 'load')
if not course.allow_public_wiki_access:
is_enrolled = CourseEnrollment.is_enrolled(request.user, course.id)
is_staff = has_access(request.user, course, 'staff')
if not (is_enrolled or is_staff):
raise PermissionDenied()
prepend_string = '/courses/' + course_id
wiki_reverse._transform_url = lambda url: prepend_string + url wiki_reverse._transform_url = lambda url: prepend_string + url
return None return None
......
...@@ -5,6 +5,8 @@ from courseware.tests.tests import LoginEnrollmentTestCase ...@@ -5,6 +5,8 @@ from courseware.tests.tests import LoginEnrollmentTestCase
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from mock import patch
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class WikiRedirectTestCase(LoginEnrollmentTestCase): class WikiRedirectTestCase(LoginEnrollmentTestCase):
...@@ -23,6 +25,7 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase): ...@@ -23,6 +25,7 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase):
self.activate_user(self.student) self.activate_user(self.student)
self.activate_user(self.instructor) self.activate_user(self.instructor)
@patch.dict("django.conf.settings.FEATURES", {'ALLOW_WIKI_ROOT_ACCESS': True})
def test_wiki_redirect(self): def test_wiki_redirect(self):
""" """
Test that requesting wiki URLs redirect properly to or out of classes. Test that requesting wiki URLs redirect properly to or out of classes.
...@@ -60,6 +63,22 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase): ...@@ -60,6 +63,22 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase):
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
self.assertEqual(resp['Location'], 'http://testserver' + destination) self.assertEqual(resp['Location'], 'http://testserver' + destination)
@patch.dict("django.conf.settings.FEATURES", {'ALLOW_WIKI_ROOT_ACCESS': False})
def test_wiki_no_root_access(self):
"""
Test to verify that normally Wiki's cannot be browsed from the /wiki/xxxx/yyy/zz URLs
"""
self.login(self.student, self.password)
self.enroll(self.toy)
referer = reverse("progress", kwargs={'course_id': self.toy.id})
destination = reverse("wiki:get", kwargs={'path': 'some/fake/wiki/page/'})
resp = self.client.get(destination, HTTP_REFERER=referer)
self.assertEqual(resp.status_code, 403)
def create_course_page(self, course): def create_course_page(self, course):
""" """
Test that loading the course wiki page creates the wiki page. Test that loading the course wiki page creates the wiki page.
...@@ -88,6 +107,7 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase): ...@@ -88,6 +107,7 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase):
self.assertContains(resp, "Course Info") self.assertContains(resp, "Course Info")
self.assertContains(resp, "courseware") self.assertContains(resp, "courseware")
@patch.dict("django.conf.settings.FEATURES", {'ALLOW_WIKI_ROOT_ACCESS': True})
def test_course_navigator(self): def test_course_navigator(self):
"""" """"
Test that going from a course page to a wiki page contains the course navigator. Test that going from a course page to a wiki page contains the course navigator.
...@@ -103,3 +123,21 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase): ...@@ -103,3 +123,21 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase):
resp = self.client.get(course_wiki_page, follow=True, HTTP_REFERER=referer) resp = self.client.get(course_wiki_page, follow=True, HTTP_REFERER=referer)
self.has_course_navigator(resp) self.has_course_navigator(resp)
@patch.dict("django.conf.settings.FEATURES", {'ALLOW_WIKI_ROOT_ACCESS': True})
def test_wiki_not_accessible_when_not_enrolled(self):
""""
Test that going from a course page to a wiki page contains the course navigator.
"""
self.login(self.instructor, self.password)
self.enroll(self.toy)
self.create_course_page(self.toy)
self.login(self.student, self.password)
course_wiki_page = reverse('wiki:get', kwargs={'path': self.toy.wiki_slug + '/'})
referer = reverse("courseware", kwargs={'course_id': self.toy.id})
resp = self.client.get(course_wiki_page, follow=True, HTTP_REFERER=referer)
self.assertEquals(resp.status_code, 403)
...@@ -224,6 +224,11 @@ FEATURES = { ...@@ -224,6 +224,11 @@ FEATURES = {
# Toggle embargo functionality # Toggle embargo functionality
'EMBARGO': False, 'EMBARGO': False,
# Whether the Wiki subsystem should be accessible via the direct /wiki/ paths. Setting this to True means
# that people can submit content and modify the Wiki in any arbitrary manner. We're leaving this as True in the
# defaults, so that we maintain current behavior
'ALLOW_WIKI_ROOT_ACCESS': True,
} }
# Used for A/B testing # Used for A/B testing
......
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