import datetime import pytz from mock import patch from django.contrib.auth.models import User, Group from django.core.urlresolvers import reverse from django.test.utils import override_settings # Need access to internal func to put users in the right group from courseware.access import (has_access, _course_staff_group_name, course_beta_test_group_name, settings as access_settings) from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from helpers import LoginEnrollmentTestCase, check_for_get_code from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Check that view authentication works properly. """ ACCOUNT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')] @staticmethod def _reverse_urls(names, course): """ Reverse a list of course urls. `names` is a list of URL names that correspond to sections in a course. `course` is the instance of CourseDescriptor whose section URLs are to be returned. Returns a list URLs corresponding to section in the passed in course. """ return [reverse(name, kwargs={'course_id': course.id}) for name in names] def _check_non_staff_light(self, course): """ Check that non-staff have access to light urls. `course` is an instance of CourseDescriptor. """ urls = [reverse('about_course', kwargs={'course_id': course.id}), reverse('courses')] for url in urls: check_for_get_code(self, 200, url) def _check_non_staff_dark(self, course): """ Check that non-staff don't have access to dark urls. """ names = ['courseware', 'instructor_dashboard', 'progress'] urls = self._reverse_urls(names, course) urls.extend([ reverse('book', kwargs={'course_id': course.id, 'book_index': index}) for index, book in enumerate(course.textbooks) ]) for url in urls: check_for_get_code(self, 404, url) def _check_staff(self, course): """ Check that access is right for staff in course. """ names = ['about_course', 'instructor_dashboard', 'progress'] urls = self._reverse_urls(names, course) urls.extend([ reverse('book', kwargs={'course_id': course.id, 'book_index': index}) for index, book in enumerate(course.textbooks) ]) for url in urls: check_for_get_code(self, 200, url) # The student progress tab is not accessible to a student # before launch, so the instructor view-as-student feature # should return a 404 as well. # TODO (vshnayder): If this is not the behavior we want, will need # to make access checking smarter and understand both the effective # user (the student), and the requesting user (the prof) url = reverse('student_progress', kwargs={'course_id': course.id, 'student_id': User.objects.get(email=self.ACCOUNT_INFO[0][0]).id}) check_for_get_code(self, 404, url) # The courseware url should redirect, not 200 url = self._reverse_urls(['courseware'], course)[0] check_for_get_code(self, 302, url) def setUp(self): self.course = CourseFactory.create(number='999', display_name='Robot_Super_Course') self.overview_chapter = ItemFactory.create(display_name='Overview') self.courseware_chapter = ItemFactory.create(display_name='courseware') self.test_course = CourseFactory.create(number='666', display_name='Robot_Sub_Course') self.sub_courseware_chapter = ItemFactory.create(parent_location=self.test_course.location, display_name='courseware') self.sub_overview_chapter = ItemFactory.create(parent_location=self.sub_courseware_chapter.location, display_name='Overview') self.welcome_section = ItemFactory.create(parent_location=self.overview_chapter.location, display_name='Welcome') # Create two accounts and activate them. for i in range(len(self.ACCOUNT_INFO)): username, email, password = 'u{0}'.format(i), self.ACCOUNT_INFO[i][0], self.ACCOUNT_INFO[i][1] self.create_account(username, email, password) self.activate_user(email) def test_redirection_unenrolled(self): """ Verify unenrolled student is redirected to the 'about' section of the chapter instead of the 'Welcome' section after clicking on the courseware tab. """ email, password = self.ACCOUNT_INFO[0] self.login(email, password) response = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) self.assertRedirects(response, reverse('about_course', args=[self.course.id])) def test_redirection_enrolled(self): """ Verify enrolled student is redirected to the 'Welcome' section of the chapter after clicking on the courseware tab. """ email, password = self.ACCOUNT_INFO[0] self.login(email, password) self.enroll(self.course) response = self.client.get(reverse('courseware', kwargs={'course_id': self.course.id})) self.assertRedirects(response, reverse('courseware_section', kwargs={'course_id': self.course.id, 'chapter': 'Overview', 'section': 'Welcome'})) def test_instructor_page_access_nonstaff(self): """ Verify non-staff cannot load the instructor dashboard, the grade views, and student profile pages. """ email, password = self.ACCOUNT_INFO[0] self.login(email, password) self.enroll(self.course) self.enroll(self.test_course) urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}), reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})] # Shouldn't be able to get to the instructor pages for url in urls: check_for_get_code(self, 404, url) def test_instructor_course_access(self): """ Verify instructor can load the instructor dashboard, the grade views, and student profile pages for their course. """ email, password = self.ACCOUNT_INFO[1] # Make the instructor staff in self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(User.objects.get(email=email)) self.login(email, password) # Now should be able to get to self.course, but not self.test_course url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) check_for_get_code(self, 200, url) url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id}) check_for_get_code(self, 404, url) def test_instructor_as_staff_access(self): """ Verify the instructor can load staff pages if he is given staff permissions. """ email, password = self.ACCOUNT_INFO[1] self.login(email, password) # now make the instructor also staff instructor = User.objects.get(email=email) instructor.is_staff = True instructor.save() # and now should be able to load both urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}), reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})] for url in urls: check_for_get_code(self, 200, url) @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) def test_dark_launch_enrolled_student(self): """ Make sure that before course start, students can't access course pages. """ student_email, student_password = self.ACCOUNT_INFO[0] # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) course_data = {'start': tomorrow} test_course_data = {'start': tomorrow} self.course = self.update_course(self.course, course_data) self.test_course = self.update_course(self.test_course, test_course_data) self.assertFalse(self.course.has_started()) self.assertFalse(self.test_course.has_started()) # First, try with an enrolled student self.login(student_email, student_password) self.enroll(self.course, True) self.enroll(self.test_course, True) # shouldn't be able to get to anything except the light pages self._check_non_staff_light(self.course) self._check_non_staff_dark(self.course) self._check_non_staff_light(self.test_course) self._check_non_staff_dark(self.test_course) @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) def test_dark_launch_instructor(self): """ Make sure that before course start instructors can access the page for their course. """ instructor_email, instructor_password = self.ACCOUNT_INFO[1] now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) course_data = {'start': tomorrow} test_course_data = {'start': tomorrow} self.course = self.update_course(self.course, course_data) self.test_course = self.update_course(self.test_course, test_course_data) # Make the instructor staff in self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(User.objects.get(email=instructor_email)) self.logout() self.login(instructor_email, instructor_password) # Enroll in the classes---can't see courseware otherwise. self.enroll(self.course, True) self.enroll(self.test_course, True) # should now be able to get to everything for self.course self._check_non_staff_light(self.test_course) self._check_non_staff_dark(self.test_course) self._check_staff(self.course) @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) def test_dark_launch_staff(self): """ Make sure that before course start staff can access course pages. """ instructor_email, instructor_password = self.ACCOUNT_INFO[1] now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) course_data = {'start': tomorrow} test_course_data = {'start': tomorrow} self.course = self.update_course(self.course, course_data) self.test_course = self.update_course(self.test_course, test_course_data) self.login(instructor_email, instructor_password) self.enroll(self.course, True) self.enroll(self.test_course, True) # now also make the instructor staff instructor = User.objects.get(email=instructor_email) instructor.is_staff = True instructor.save() # and now should be able to load both self._check_staff(self.course) self._check_staff(self.test_course) @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) def test_enrollment_period(self): """ Check that enrollment periods work. """ student_email, student_password = self.ACCOUNT_INFO[0] instructor_email, instructor_password = self.ACCOUNT_INFO[1] # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) nextday = tomorrow + datetime.timedelta(days=1) yesterday = now - datetime.timedelta(days=1) course_data = {'enrollment_start': tomorrow, 'enrollment_end': nextday} test_course_data = {'enrollment_start': yesterday, 'enrollment_end': tomorrow} # self.course's enrollment period hasn't started self.course = self.update_course(self.course, course_data) # test_course course's has self.test_course = self.update_course(self.test_course, test_course_data) # First, try with an enrolled student self.login(student_email, student_password) self.assertFalse(self.enroll(self.course)) self.assertTrue(self.enroll(self.test_course)) # Make the instructor staff in the self.course group_name = _course_staff_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(User.objects.get(email=instructor_email)) self.logout() self.login(instructor_email, instructor_password) self.assertTrue(self.enroll(self.course)) # now make the instructor global staff, but not in the instructor group group.user_set.remove(User.objects.get(email=instructor_email)) instructor = User.objects.get(email=instructor_email) instructor.is_staff = True instructor.save() # unenroll and try again self.unenroll(self.course) self.assertTrue(self.enroll(self.course)) @patch.dict(access_settings.MITX_FEATURES, {'DISABLE_START_DATES': False}) def test_beta_period(self): """ Check that beta-test access works. """ student_email, student_password = self.ACCOUNT_INFO[0] instructor_email, instructor_password = self.ACCOUNT_INFO[1] # Make courses start in the future now = datetime.datetime.now(pytz.UTC) tomorrow = now + datetime.timedelta(days=1) course_data = {'start': tomorrow} # self.course's hasn't started self.course = self.update_course(self.course, course_data) self.assertFalse(self.course.has_started()) # but should be accessible for beta testers self.course.days_early_for_beta = 2 # student user shouldn't see it student_user = User.objects.get(email=student_email) self.assertFalse(has_access(student_user, self.course, 'load')) # now add the student to the beta test group group_name = course_beta_test_group_name(self.course.location) group = Group.objects.create(name=group_name) group.user_set.add(student_user) # now the student should see it self.assertTrue(has_access(student_user, self.course, 'load'))