Commit 9c2c8d54 by Jason Bau Committed by stv

Limit sneakpeek middleware from acting on API-like request

Impose the following restrictions on setting up sneakpeek
* Has to be a GET
* Cannot be a machine callback to handler from xqueue or xblock
* Cannot be trying to list the LTI endpoints
parent 1d8ff75b
...@@ -575,6 +575,7 @@ def setup_sneakpeek(request, course_id): ...@@ -575,6 +575,7 @@ def setup_sneakpeek(request, course_id):
if not CoursePreference.course_allows_nonregistered_access(course_key): if not CoursePreference.course_allows_nonregistered_access(course_key):
return HttpResponseForbidden("Cannot access the course") return HttpResponseForbidden("Cannot access the course")
if not request.user.is_authenticated(): if not request.user.is_authenticated():
# if there's no user, create a nonregistered user # if there's no user, create a nonregistered user
_create_and_login_nonregistered_user(request) _create_and_login_nonregistered_user(request)
...@@ -582,6 +583,7 @@ def setup_sneakpeek(request, course_id): ...@@ -582,6 +583,7 @@ def setup_sneakpeek(request, course_id):
# registered users can't sneakpeek, so log them out and create a new nonregistered user # registered users can't sneakpeek, so log them out and create a new nonregistered user
logout(request) logout(request)
_create_and_login_nonregistered_user(request) _create_and_login_nonregistered_user(request)
# fall-through case is a sneakpeek user that's already logged in
can_enroll, error_msg = _check_can_enroll_in_course(request.user, can_enroll, error_msg = _check_can_enroll_in_course(request.user,
course_key, course_key,
......
...@@ -2,11 +2,17 @@ ...@@ -2,11 +2,17 @@
Middleware class that supports deep-links for allows courses that allow sneakpeek. Middleware class that supports deep-links for allows courses that allow sneakpeek.
The user will be registered anonymously, logged in, and enrolled in the course The user will be registered anonymously, logged in, and enrolled in the course
""" """
from django.core.urlresolvers import resolve
from django.http import Http404
from student.models import CourseEnrollment from student.models import CourseEnrollment
from util.request import course_id_from_url from util.request import course_id_from_url
from courseware.models import CoursePreference from courseware.models import CoursePreference
from student.views import _create_and_login_nonregistered_user, _check_can_enroll_in_course from student.views import _create_and_login_nonregistered_user, _check_can_enroll_in_course
DISALLOW_SNEAKPEEK_URL_NAMES = ('lti_rest_endpoints', 'xblock_handler_noauth', 'xqueue_callback')
class SneakPeekDeepLinkMiddleware(object): class SneakPeekDeepLinkMiddleware(object):
""" """
Sneak Peak Deep Link Middlware Sneak Peak Deep Link Middlware
...@@ -14,10 +20,12 @@ class SneakPeekDeepLinkMiddleware(object): ...@@ -14,10 +20,12 @@ class SneakPeekDeepLinkMiddleware(object):
def process_request(self, request): def process_request(self, request):
""" """
Only if the following are all true: Only if the following are all true:
1. request.user is AnonymousUser (This middleware must be added after the AuthenticationMiddleware) 1. request is a GET
2. request has a course context 2. request is NOT to a URL in DISALLOW_SNEAKPEEK_URL_NAMES
3. request's course allows sneakpeek 3. request.user is AnonymousUser (This middleware must be added after the AuthenticationMiddleware)
4. request's course's enrollment period is open 4. request has a course context
5. request's course allows sneakpeek
6. request's course's enrollment period is open
Does the following: Does the following:
1. Registers an anonymous user 1. Registers an anonymous user
2. Login this user in 2. Login this user in
...@@ -25,6 +33,16 @@ class SneakPeekDeepLinkMiddleware(object): ...@@ -25,6 +33,16 @@ class SneakPeekDeepLinkMiddleware(object):
""" """
### Start by testing the conditions, each of which can fail fast and return, ### Start by testing the conditions, each of which can fail fast and return,
### causing the middleware to do nothing ### causing the middleware to do nothing
if request.method != "GET":
return None
try:
match = resolve(request.path)
if match.url_name in DISALLOW_SNEAKPEEK_URL_NAMES:
return None
except Http404:
pass
if request.user.is_authenticated(): if request.user.is_authenticated():
return None return None
......
"""
Tests for deeplink middleware for sneakpeek
"""
import mock
import pygeoip
import unittest
from datetime import datetime, timedelta
from django.conf import settings
from django.test import TestCase, Client
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.contrib.auth.models import AnonymousUser
from django.utils.importlib import import_module
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
from student.models import CourseEnrollment, UserProfile
from courseware.models import CoursePreference
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.django import editable_modulestore
from sneakpeek_deeplink.middleware import SneakPeekDeepLinkMiddleware
NO_SNEAKPEEK_PATHS = [
'/courses/open/course/run/lti_rest_endpoints/', # lti_rest_endpoint
'/courses/open/course/run/xblock/usage_id/handler_noauth/handler', # xblock_handler_noauth
'/courses/open/course/run/xqueue/user_id/mod_id/dispatch',
]
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
class SneakPeekDeeplinkMiddlewareTests(TestCase):
"""
Tests of Sneakpek deeplink middleware
"""
def setUp(self):
self.store = editable_modulestore()
self.client = Client()
self.factory = RequestFactory()
self.middleware = SneakPeekDeepLinkMiddleware()
month = timedelta(days=30)
month2 = timedelta(days=60)
now = datetime.now()
self.nonsneakpeek_course = CourseFactory.create(
org='nonsneakpeek',
number='course',
run='run',
enrollment_start=now - month,
enrollment_end=now + month)
self.nonsneakpeek_course.save()
self.open_course = CourseFactory.create(
org='open',
number='course',
run='run',
enrollment_start=now - month,
enrollment_end=now + month)
self.open_course.save()
self.closed_course = CourseFactory.create(
org='closed',
number='course',
run='run',
enrollment_start=now - month2,
enrollment_end=now - month)
self.closed_course.save()
CoursePreference(
course_id=self.open_course.id,
pref_key="allow_nonregistered_access",
pref_value="true",
).save()
CoursePreference(
course_id=self.closed_course.id,
pref_key="allow_nonregistered_access",
pref_value="true",
).save()
def make_successful_sneakpeek_login_request(self):
"""
This returns a request which will cause a sneakpeek to happen
"""
sneakpeek_path = '/courses/open/course/run/info'
req = self.factory.get(sneakpeek_path)
req.session = import_module(settings.SESSION_ENGINE).SessionStore() # empty session
req.user = AnonymousUser()
return req
def assertSuccessfulSneakPeek(self, request, course):
self.assertTrue(request.user.is_authenticated())
self.assertFalse(UserProfile.has_registered(request.user))
self.assertTrue(CourseEnrollment.is_enrolled(request.user, course.id))
def assertNoSneakPeek(self, request, course, check_auth=True):
if check_auth:
self.assertFalse(request.user.is_authenticated())
self.assertEquals(0, CourseEnrollment.objects.filter(course_id=course.id).count())
def test_sneakpeek_success(self):
req = self.make_successful_sneakpeek_login_request()
self.assertIsNone(self.middleware.process_request(req))
self.assertSuccessfulSneakPeek(req, self.open_course)
def test_non_get(self):
req = self.make_successful_sneakpeek_login_request()
req.method = "POST"
self.assertIsNone(self.middleware.process_request(req))
self.assertNoSneakPeek(req, self.open_course)
def test_get_404(self):
req = self.make_successful_sneakpeek_login_request()
req.path = '/foobarmew'
self.assertIsNone(self.middleware.process_request(req))
self.assertNoSneakPeek(req, self.open_course)
def test_url_blacklists(self):
for path in NO_SNEAKPEEK_PATHS:
req = self.make_successful_sneakpeek_login_request()
req.path = path
self.assertIsNone(self.middleware.process_request(req))
self.assertNoSneakPeek(req, self.open_course)
def test_authenticated_user(self):
req = self.make_successful_sneakpeek_login_request()
user = UserFactory()
user.save()
req.user = user
self.assertIsNone(self.middleware.process_request(req))
self.assertNoSneakPeek(req, self.open_course, check_auth=False)
def test_noncourse_path(self):
req = self.make_successful_sneakpeek_login_request()
req.path = "/dashboard"
self.assertIsNone(self.middleware.process_request(req))
self.assertNoSneakPeek(req, self.open_course)
def test_nonsneakpeek_course(self):
req = self.make_successful_sneakpeek_login_request()
req.path = '/courses/nonsneakpeek/course/run/info'
self.assertIsNone(self.middleware.process_request(req))
self.assertNoSneakPeek(req, self.nonsneakpeek_course)
def test_sneakpeek_course_enrollment_closed(self):
req = self.make_successful_sneakpeek_login_request()
req.path = '/courses/closed/course/run/info'
self.assertIsNone(self.middleware.process_request(req))
self.assertNoSneakPeek(req, self.closed_course)
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