Example #1
0
 def setUp(self):
     self.email = '*****@*****.**'
     self.pw = 'xyz'
     self.username = '******'
     self.client = AjaxEnabledTestClient()
     # clear the cache so ratelimiting won't affect these tests
     cache.clear()
Example #2
0
    def test_private_pages_auth(self):
        """Make sure pages that do require login work."""
        auth_pages = ('/course', )

        # These are pages that should just load when the user is logged in
        # (no data needed)
        simple_auth_pages = ('/course', )

        # need an activated user
        self.test_create_account()

        # Create a new session
        self.client = AjaxEnabledTestClient()

        # Not logged in.  Should redirect to login.
        print('Not logged in')
        for page in auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=302)

        # Logged in should work.
        self.login(self.email, self.pw)

        print('Logged in')
        for page in simple_auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=200)
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')
        self.course_create_rerun_url = reverse('course_handler')
        self.course_start = datetime.datetime.utcnow()
        self.course_end = self.course_start + datetime.timedelta(days=30)
        self.enrollment_start = self.course_start - datetime.timedelta(days=7)
        self.enrollment_end = self.course_end - datetime.timedelta(days=14)
        source_course = CourseFactory.create(
            org='origin',
            number='the_beginning',
            run='first',
            display_name='the one and only',
            start=self.course_start,
            end=self.course_end,
            enrollment_start=self.enrollment_start,
            enrollment_end=self.enrollment_end
        )
        self.source_course_key = source_course.id

        for role in [CourseInstructorRole, CourseStaffRole]:
            role(self.source_course_key).add_users(self.user)
Example #4
0
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        super(TestCourseAccess, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username,
                          password=self.user_password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_key = self.store.make_course_key('myu', 'mydept.mycourse',
                                                     'myrun')
        course_url = reverse_url('course_handler')
        self.client.ajax_post(
            course_url,
            {
                'org': self.course_key.org,
                'number': self.course_key.course,
                'display_name': 'My favorite course',
                'run': self.course_key.run,
            },
        )

        self.users = self._create_users()
Example #5
0
    def setUp(self):
        super(AuthTestCase, self).setUp(create_user=False)

        self.email = '*****@*****.**'
        self.pw = 'xyz'
        self.username = '******'
        self.client = AjaxEnabledTestClient()
        # clear the cache so ratelimiting won't affect these tests
        cache.clear()
Example #6
0
    def test_course_plain_english(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html('/home/')
        self.assertContains(resp,
                            '<h1 class="page-header">Studio Home</h1>',
                            status_code=200,
                            html=True)
    def setUp(self):
        user_password = super(LibraryTestCase, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=user_password)

        self.lib_key = self._create_library()
        self.library = modulestore().get_library(self.lib_key)

        self.session_data = {}  # Used by _bind_module
Example #8
0
    def setUp(self):
        super(LibraryTestCase, self).setUp()

        self.client = AjaxEnabledTestClient()
        self._login_as_staff_user(logout_first=False)

        self.lib_key = self._create_library()
        self.library = modulestore().get_library(self.lib_key)

        self.session_data = {}  # Used by _bind_module
 def setUp(self):
     """
     Add a user and a course
     """
     super(TestCourseListing, self).setUp()
     # create and log in a staff user.
     self.user = UserFactory(is_staff=True)  # pylint: disable=no-member
     self.factory = RequestFactory()
     self.client = AjaxEnabledTestClient()
     self.client.login(username=self.user.username, password='******')
Example #10
0
    def test_course_explicit_english(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html('/course', {}, HTTP_ACCEPT_LANGUAGE='en')

        self.assertContains(resp,
                            '<h1 class="page-header">My Courses</h1>',
                            status_code=200,
                            html=True)
Example #11
0
    def test_course_with_accents(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html('/home/', {}, HTTP_ACCEPT_LANGUAGE='eo')

        TEST_STRING = (u'<h1 class="title-1">'
                       u'My \xc7\xf6\xfcrs\xe9s L#'
                       u'</h1>')

        self.assertContains(resp, TEST_STRING, status_code=200, html=True)
Example #12
0
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestUsersDefaultRole, self).setUp()
        # create and log in a staff user.
        self.user = UserFactory(is_staff=True)  # pylint: disable=no-member
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

        # create a course via the view handler to create course
        self.course_location = Location(['i4x', 'Org_1', 'Course_1', 'course', 'Run_1'])
        self._create_course_with_given_location(self.course_location)
Example #13
0
 def setUp(self):
     """
     Add a user and a course
     """
     super(TestCourseListing, self).setUp()
     # create and log in a staff user.
     # create and log in a non-staff user
     self.user = UserFactory()
     self.factory = RequestFactory()
     self.request = self.factory.get('/course')
     self.request.user = self.user
     self.client = AjaxEnabledTestClient()
     self.client.login(username=self.user.username, password='******')
Example #14
0
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestUsersDefaultRole, self).setUp()
        # create and log in a staff user.
        self.user = UserFactory(is_staff=True)
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

        # create a course via the view handler to create course
        self.course_key = self.store.make_course_key('Org_1', 'Course_1', 'Run_1')
        self._create_course_with_given_location(self.course_key)
class TestCourseListing(ModuleStoreTestCase):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

        source_course = CourseFactory.create(
            org='origin',
            number='the_beginning',
            run='first',
            display_name='the one and only',
            start=datetime.utcnow()
        )
        self.source_course_key = source_course.id

        for role in [CourseInstructorRole, CourseStaffRole]:
            role(self.source_course_key).add_users(self.user)

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_rerun(self):
        """
        Just testing the functionality the view handler adds over the tasks tested in test_clone_course
        """
        response = self.client.ajax_post('/course/', {
            'source_course_key': unicode(self.source_course_key),
            'org': self.source_course_key.org, 'course': self.source_course_key.course, 'run': 'copy',
            'display_name': 'not the same old name',
        })
        self.assertEqual(response.status_code, 200)
        data = parse_json(response)
        dest_course_key = CourseKey.from_string(data['destination_course_key'])

        self.assertEqual(dest_course_key.run, 'copy')
        dest_course = self.store.get_course(dest_course_key)
        self.assertEqual(dest_course.start, CourseFields.start.default)
Example #16
0
    def test_handle_requests(self, aside_key_class):
        """
        Checks that handler to save tags in StructuredTagsAside works properly
        """
        handler_url = reverse_usage_url(
            'preview_handler',
            unicode(aside_key_class(self.problem.location, self.aside_name)),
            kwargs={'handler': 'save_tags'}
        )

        client = AjaxEnabledTestClient()
        client.login(username=self.user.username, password=self.user_password)

        response = client.post(handler_url, json.dumps({}), content_type="application/json")
        self.assertEqual(response.status_code, 400)

        response = client.post(handler_url, json.dumps({'undefined_tag': ['undefined1', 'undefined2']}),
                               content_type="application/json")
        self.assertEqual(response.status_code, 400)

        response = client.post(handler_url, json.dumps({self.aside_tag_dif: ['undefined1', 'undefined2']}),
                               content_type="application/json")
        self.assertEqual(response.status_code, 400)

        def _test_helper_func(problem_location):
            """
            Helper function
            """
            problem = modulestore().get_item(problem_location)
            asides = problem.runtime.get_asides(problem)
            tag_aside = None
            for aside in asides:
                if isinstance(aside, StructuredTagsAside):
                    tag_aside = aside
                    break
            return tag_aside

        response = client.post(handler_url, json.dumps({self.aside_tag_dif: [self.aside_tag_dif_value]}),
                               content_type="application/json")
        self.assertEqual(response.status_code, 200)

        tag_aside = _test_helper_func(self.problem.location)
        self.assertIsNotNone(tag_aside, "Necessary StructuredTagsAside object isn't found")
        self.assertEqual(tag_aside.saved_tags[self.aside_tag_dif], [self.aside_tag_dif_value])

        response = client.post(handler_url, json.dumps({self.aside_tag_dif: [self.aside_tag_dif_value,
                                                                             self.aside_tag_dif_value2]}),
                               content_type="application/json")
        self.assertEqual(response.status_code, 200)

        tag_aside = _test_helper_func(self.problem.location)
        self.assertIsNotNone(tag_aside, "Necessary StructuredTagsAside object isn't found")
        self.assertEqual(tag_aside.saved_tags[self.aside_tag_dif], [self.aside_tag_dif_value,
                                                                    self.aside_tag_dif_value2])
Example #17
0
 def test_contentstore_views_entrance_exam_get_invalid_user(self):
     """
     Unit Test: test_contentstore_views_entrance_exam_get_invalid_user
     """
     user = User.objects.create(
         username='******',
         email='*****@*****.**',
         is_active=True,
     )
     user.set_password('test')
     user.save()
     self.client = AjaxEnabledTestClient()
     self.client.login(username='******', password='******')
     resp = self.client.get(self.exam_url)
     self.assertEqual(resp.status_code, 403)
Example #18
0
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        user_password = super(TestCourseAccess, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=user_password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_key = self.store.make_course_key("myu", "mydept.mycourse", "myrun")
        course_url = reverse_url("course_handler")
        self.client.ajax_post(
            course_url,
            {
                "org": self.course_key.org,
                "number": self.course_key.course,
                "display_name": "My favorite course",
                "run": self.course_key.run,
            },
        )

        self.users = self._create_users()
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')
        self.course_create_rerun_url = reverse('course_handler')
        self.course_start = datetime.datetime.utcnow()
        self.course_end = self.course_start + datetime.timedelta(days=30)
        self.enrollment_start = self.course_start - datetime.timedelta(days=7)
        self.enrollment_end = self.course_end - datetime.timedelta(days=14)
        source_course = CourseFactory.create(
            org='origin',
            number='the_beginning',
            run='first',
            display_name='the one and only',
            start=self.course_start,
            end=self.course_end,
            enrollment_start=self.enrollment_start,
            enrollment_end=self.enrollment_end
        )
        self.source_course_key = source_course.id

        for role in [CourseInstructorRole, CourseStaffRole]:
            role(self.source_course_key).add_users(self.user)
Example #20
0
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        user_password = super(TestCourseAccess, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=user_password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_key = self.store.make_course_key('myu', 'mydept.mycourse', 'myrun')
        course_url = reverse_url('course_handler')
        with mock.patch.dict('django.conf.settings.FEATURES', {"DEFAULT_STORE_FOR_NEW_COURSE": None}):
            self.client.ajax_post(
                course_url,
                {
                    'org': self.course_key.org,
                    'number': self.course_key.course,
                    'display_name': 'My favorite course',
                    'run': self.course_key.run,
                }
            )

        self.users = self._create_users()
Example #21
0
    def test_course_plain_english(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html("/course/")
        self.assertContains(resp, '<h1 class="page-header">My Courses</h1>', status_code=200, html=True)
Example #22
0
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        user_password = super(TestCourseAccess, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=user_password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_key = SlashSeparatedCourseKey('myu', 'mydept.mycourse', 'myrun')
        course_url = reverse_url('course_handler')
        self.client.ajax_post(
            course_url,
            {
                'org': self.course_key.org,
                'number': self.course_key.course,
                'display_name': 'My favorite course',
                'run': self.course_key.run,
            },
        )

        self.users = self._create_users()
Example #23
0
 def setUp(self):
     self.email = '*****@*****.**'
     self.pw = 'xyz'
     self.username = '******'
     self.client = AjaxEnabledTestClient()
     # clear the cache so ratelimiting won't affect these tests
     cache.clear()
Example #24
0
    def test_private_pages_auth(self):
        """Make sure pages that do require login work."""
        auth_pages = (
            '/course/',
        )

        # These are pages that should just load when the user is logged in
        # (no data needed)
        simple_auth_pages = (
            '/course/',
        )

        # need an activated user
        self.test_create_account()

        # Create a new session
        self.client = AjaxEnabledTestClient()

        # Not logged in.  Should redirect to login.
        print('Not logged in')
        for page in auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=302)

        # Logged in should work.
        self.login(self.email, self.pw)

        print('Logged in')
        for page in simple_auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=200)
Example #25
0
    def setUp(self):
        super(AuthTestCase, self).setUp(create_user=False)

        self.email = '*****@*****.**'
        self.pw = 'xyz'
        self.username = '******'
        self.client = AjaxEnabledTestClient()
        # clear the cache so ratelimiting won't affect these tests
        cache.clear()
Example #26
0
    def test_handle_requests(self, aside_key_class):
        """
        Checks that handler to save tags in StructuredTagsAside works properly
        """
        handler_url = reverse_usage_url(
            'component_handler',
            unicode(aside_key_class(self.problem.location, self.aside_name)),
            kwargs={'handler': 'save_tags'}
        )

        client = AjaxEnabledTestClient()
        client.login(username=self.user.username, password=self.user_password)

        response = client.post(handler_url, json.dumps({}), content_type="application/json")
        self.assertEqual(response.status_code, 400)

        response = client.post(handler_url, json.dumps({'undefined_tag': ['undefined1', 'undefined2']}),
                               content_type="application/json")
        self.assertEqual(response.status_code, 400)

        response = client.post(handler_url, json.dumps({self.aside_tag_dif: ['undefined1', 'undefined2']}),
                               content_type="application/json")
        self.assertEqual(response.status_code, 400)

        def _test_helper_func(problem_location):
            """
            Helper function
            """
            problem = modulestore().get_item(problem_location)
            asides = problem.runtime.get_asides(problem)
            tag_aside = None
            for aside in asides:
                if isinstance(aside, StructuredTagsAside):
                    tag_aside = aside
                    break
            return tag_aside

        response = client.post(handler_url, json.dumps({self.aside_tag_dif: [self.aside_tag_dif_value]}),
                               content_type="application/json")
        self.assertEqual(response.status_code, 200)

        tag_aside = _test_helper_func(self.problem.location)
        self.assertIsNotNone(tag_aside, "Necessary StructuredTagsAside object isn't found")
        self.assertEqual(tag_aside.saved_tags[self.aside_tag_dif], [self.aside_tag_dif_value])

        response = client.post(handler_url, json.dumps({self.aside_tag_dif: [self.aside_tag_dif_value,
                                                                             self.aside_tag_dif_value2]}),
                               content_type="application/json")
        self.assertEqual(response.status_code, 200)

        tag_aside = _test_helper_func(self.problem.location)
        self.assertIsNotNone(tag_aside, "Necessary StructuredTagsAside object isn't found")
        self.assertEqual(tag_aside.saved_tags[self.aside_tag_dif], [self.aside_tag_dif_value,
                                                                    self.aside_tag_dif_value2])
Example #27
0
    def setUp(self):
        super(LibraryTestCase, self).setUp()

        self.client = AjaxEnabledTestClient()
        self._login_as_staff_user(logout_first=False)

        self.lib_key = self._create_library()
        self.library = modulestore().get_library(self.lib_key)

        self.session_data = {}  # Used by _bind_module
Example #28
0
    def test_course_with_accents(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html("/course/", {}, HTTP_ACCEPT_LANGUAGE="eo")

        TEST_STRING = u'<h1 class="title-1">' u"My \xc7\xf6\xfcrs\xe9s L#" u"</h1>"

        self.assertContains(resp, TEST_STRING, status_code=200, html=True)
 def setUp(self):
     """
     Add a user and a course
     """
     super(TestCourseListing, self).setUp()
     # create and log in a staff user.
     self.user = UserFactory(is_staff=True)  # pylint: disable=no-member
     self.factory = RequestFactory()
     self.client = AjaxEnabledTestClient()
     self.client.login(username=self.user.username, password='******')
Example #30
0
    def setUp(self):
        user_password = super(LibraryTestCase, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=user_password)

        self.lib_key = self._create_library()
        self.library = modulestore().get_library(self.lib_key)

        self.session_data = {}  # Used by _bind_module
Example #31
0
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

        source_course = CourseFactory.create(org='origin',
                                             number='the_beginning',
                                             run='first',
                                             display_name='the one and only',
                                             start=datetime.utcnow())
        self.source_course_key = source_course.id

        for role in [CourseInstructorRole, CourseStaffRole]:
            role(self.source_course_key).add_users(self.user)
Example #32
0
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        super(TestCourseAccess, self).setUp()
        uname = 'testuser'
        email = '*****@*****.**'
        password = '******'

        # Create the use so we can log them in.
        self.user = User.objects.create_user(uname, email, password)

        # Note that we do not actually need to do anything
        # for registration if we directly mark them active.
        self.user.is_active = True
        # Staff has access to view all courses
        self.user.is_staff = True
        self.user.save()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=uname, password=password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_location = Location(['i4x', 'myu', 'mydept.mycourse', 'course', 'myrun'])
        self.course_locator = loc_mapper().translate_location(
            self.course_location.course_id, self.course_location, False, True
        )
        self.client.ajax_post(
            self.course_locator.url_reverse('course'),
            {
                'org': self.course_location.org, 
                'number': self.course_location.course,
                'display_name': 'My favorite course',
                'run': self.course_location.name,
            }
        )

        self.users = self._create_users()
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestUsersDefaultRole, self).setUp()
        # create and log in a staff user.
        self.user = UserFactory(is_staff=True)  # pylint: disable=no-member
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password="******")

        # create a course via the view handler to create course
        self.course_key = SlashSeparatedCourseKey("Org_1", "Course_1", "Run_1")
        self._create_course_with_given_location(self.course_key)
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestUsersDefaultRole, self).setUp()
        # create and log in a staff user.
        self.user = UserFactory(is_staff=True)  # pylint: disable=no-member
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

        # create a course via the view handler to create course
        self.course_key = self.store.make_course_key('Org_1', 'Course_1', 'Run_1')
        self._create_course_with_given_location(self.course_key)
Example #35
0
 def setUp(self):
     """
     Add a user and a course
     """
     super(TestCourseListing, self).setUp()
     # create and log in a staff user.
     # create and log in a non-staff user
     self.user = UserFactory()
     self.factory = RequestFactory()
     self.request = self.factory.get('/course')
     self.request.user = self.user
     self.client = AjaxEnabledTestClient()
     self.client.login(username=self.user.username, password='******')
Example #36
0
    def test_course_explicit_english(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html('/course',
                               {},
                               HTTP_ACCEPT_LANGUAGE='en'
                               )

        self.assertContains(resp,
                            '<h1 class="page-header">My Courses</h1>',
                            status_code=200,
                            html=True)
 def test_contentstore_views_entrance_exam_get_invalid_user(self):
     """
     Unit Test: test_contentstore_views_entrance_exam_get_invalid_user
     """
     user = User.objects.create(
         username='******',
         email='*****@*****.**',
         is_active=True,
     )
     user.set_password('test')
     user.save()
     self.client = AjaxEnabledTestClient()
     self.client.login(username='******', password='******')
     resp = self.client.get(self.exam_url)
     self.assertEqual(resp.status_code, 403)
Example #38
0
    def test_handle_requests(self):
        """
        Checks that handler to save tags in StructuredTagsAside works properly
        """
        handler_url = reverse_usage_url(
            'preview_handler',
            '%s:%s::%s' % (AsideUsageKeyV1.CANONICAL_NAMESPACE,
                           self.problem.location, self.aside_name),
            kwargs={'handler': 'save_tags'})

        client = AjaxEnabledTestClient()
        client.login(username=self.user.username, password=self.user_password)

        response = client.post(path=handler_url, data={})
        self.assertEqual(response.status_code, 400)

        response = client.post(path=handler_url,
                               data={'tag': 'undefined_tag:undefined'})
        self.assertEqual(response.status_code, 400)

        val = '%s:undefined' % self.aside_tag
        response = client.post(path=handler_url, data={'tag': val})
        self.assertEqual(response.status_code, 400)

        val = '%s:%s' % (self.aside_tag, self.aside_tag_value)
        response = client.post(path=handler_url, data={'tag': val})
        self.assertEqual(response.status_code, 200)

        problem = modulestore().get_item(self.problem.location)
        asides = problem.runtime.get_asides(problem)
        tag_aside = None
        for aside in asides:
            if isinstance(aside, StructuredTagsAside):
                tag_aside = aside
                break

        self.assertIsNotNone(
            tag_aside, "Necessary StructuredTagsAside object isn't found")
        self.assertEqual(tag_aside.saved_tags[self.aside_tag],
                         self.aside_tag_value)
Example #39
0
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        super(TestCourseAccess, self).setUp()
        uname = 'testuser'
        email = '*****@*****.**'
        password = '******'

        # Create the use so we can log them in.
        self.user = User.objects.create_user(uname, email, password)

        # Note that we do not actually need to do anything
        # for registration if we directly mark them active.
        self.user.is_active = True
        # Staff has access to view all courses
        self.user.is_staff = True
        self.user.save()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=uname, password=password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_location = Location(['i4x', 'myu', 'mydept.mycourse', 'course', 'myrun'])
        self.course_locator = loc_mapper().translate_location(
            self.course_location.course_id, self.course_location, False, True
        )
        self.client.ajax_post(
            self.course_locator.url_reverse('course'),
            {
                'org': self.course_location.org, 
                'number': self.course_location.course,
                'display_name': 'My favorite course',
                'run': self.course_location.name,
            }
        )

        self.users = self._create_users()
Example #40
0
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        super(TestCourseAccess, self).setUp()
        uname = "testuser"
        email = "*****@*****.**"
        password = "******"

        # Create the use so we can log them in.
        self.user = User.objects.create_user(uname, email, password)

        # Note that we do not actually need to do anything
        # for registration if we directly mark them active.
        self.user.is_active = True
        # Staff has access to view all courses
        self.user.is_staff = True
        self.user.save()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=uname, password=password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_key = SlashSeparatedCourseKey("myu", "mydept.mycourse", "myrun")
        course_url = reverse_url("course_handler")
        self.client.ajax_post(
            course_url,
            {
                "org": self.course_key.org,
                "number": self.course_key.course,
                "display_name": "My favorite course",
                "run": self.course_key.run,
            },
        )

        self.users = self._create_users()
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

        source_course = CourseFactory.create(
            org='origin',
            number='the_beginning',
            run='first',
            display_name='the one and only',
            start=datetime.utcnow()
        )
        self.source_course_key = source_course.id

        for role in [CourseInstructorRole, CourseStaffRole]:
            role(self.source_course_key).add_users(self.user)
Example #42
0
    def test_handle_requests(self):
        """
        Checks that handler to save tags in StructuredTagsAside works properly
        """
        handler_url = reverse_usage_url(
            'preview_handler',
            '%s:%s::%s' % (AsideUsageKeyV1.CANONICAL_NAMESPACE, self.problem.location, self.aside_name),
            kwargs={'handler': 'save_tags'}
        )

        client = AjaxEnabledTestClient()
        client.login(username=self.user.username, password=self.user_password)

        response = client.post(path=handler_url, data={})
        self.assertEqual(response.status_code, 400)

        response = client.post(path=handler_url, data={'tag': 'undefined_tag:undefined'})
        self.assertEqual(response.status_code, 400)

        val = '%s:undefined' % self.aside_tag_dif
        response = client.post(path=handler_url, data={'tag': val})
        self.assertEqual(response.status_code, 400)

        val = '%s:%s' % (self.aside_tag_dif, self.aside_tag_dif_value)
        response = client.post(path=handler_url, data={'tag': val})
        self.assertEqual(response.status_code, 200)

        problem = modulestore().get_item(self.problem.location)
        asides = problem.runtime.get_asides(problem)
        tag_aside = None
        for aside in asides:
            if isinstance(aside, StructuredTagsAside):
                tag_aside = aside
                break

        self.assertIsNotNone(tag_aside, "Necessary StructuredTagsAside object isn't found")
        self.assertEqual(tag_aside.saved_tags[self.aside_tag_dif], self.aside_tag_dif_value)
Example #43
0
class AuthTestCase(ContentStoreTestCase):
    """Check that various permissions-related things work"""
    def setUp(self):
        self.email = '*****@*****.**'
        self.pw = 'xyz'
        self.username = '******'
        self.client = AjaxEnabledTestClient()
        # clear the cache so ratelimiting won't affect these tests
        cache.clear()

    def check_page_get(self, url, expected):
        resp = self.client.get_html(url)
        self.assertEqual(resp.status_code, expected)
        return resp

    def test_public_pages_load(self):
        """Make sure pages that don't require login load without error."""
        pages = (
            reverse('login'),
            reverse('signup'),
        )
        for page in pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, 200)

    def test_create_account_errors(self):
        # No post data -- should fail
        resp = self.client.post('/create_account', {})
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertEqual(data['success'], False)

    def test_create_account(self):
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)

    def test_login(self):
        self.create_account(self.username, self.email, self.pw)

        # Not activated yet.  Login should fail.
        resp = self._login(self.email, self.pw)
        data = parse_json(resp)
        self.assertFalse(data['success'])

        self.activate_user(self.email)

        # Now login should work
        self.login(self.email, self.pw)

    def test_login_ratelimited(self):
        # try logging in 30 times, the default limit in the number of failed
        # login attempts in one 5 minute period before the rate gets limited
        for i in xrange(30):
            resp = self._login(self.email, 'wrong_password{0}'.format(i))
            self.assertEqual(resp.status_code, 200)
        resp = self._login(self.email, 'wrong_password')
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertFalse(data['success'])
        self.assertIn('Too many failed login attempts.', data['value'])

    def test_login_link_on_activation_age(self):
        self.create_account(self.username, self.email, self.pw)
        # we want to test the rendering of the activation page when the user isn't logged in
        self.client.logout()
        resp = self._activate_user(self.email)
        self.assertEqual(resp.status_code, 200)

        # check the the HTML has links to the right login page. Note that this is merely a content
        # check and thus could be fragile should the wording change on this page
        expected = 'You can now <a href="' + reverse('login') + '">login</a>.'
        self.assertIn(expected, resp.content)

    def test_private_pages_auth(self):
        """Make sure pages that do require login work."""
        auth_pages = ('/course', )

        # These are pages that should just load when the user is logged in
        # (no data needed)
        simple_auth_pages = ('/course', )

        # need an activated user
        self.test_create_account()

        # Create a new session
        self.client = AjaxEnabledTestClient()

        # Not logged in.  Should redirect to login.
        print('Not logged in')
        for page in auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=302)

        # Logged in should work.
        self.login(self.email, self.pw)

        print('Logged in')
        for page in simple_auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=200)

    def test_index_auth(self):

        # not logged in.  Should return a redirect.
        resp = self.client.get_html('/course')
        self.assertEqual(resp.status_code, 302)
class TestCourseListing(ModuleStoreTestCase):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        self.user = UserFactory(is_staff=True)  # pylint: disable=no-member
        self.factory = RequestFactory()
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

    def _create_course_with_access_groups(
            self,
            course_location,
            group_name_format='group_name_with_dots',
            user=None):
        """
        Create dummy course with 'CourseFactory' and role (instructor/staff) groups with provided group_name_format
        """
        course_locator = loc_mapper().translate_location(
            course_location.course_id, course_location, False, True)
        course = CourseFactory.create(org=course_location.org,
                                      number=course_location.course,
                                      display_name=course_location.name)

        for role in [CourseInstructorRole, CourseStaffRole]:
            # pylint: disable=protected-access
            groupnames = role(course_locator)._group_names
            if group_name_format == 'group_name_with_course_name_only':
                # Create role (instructor/staff) groups with course_name only: 'instructor_run'
                group, __ = Group.objects.get_or_create(name=groupnames[2])
            elif group_name_format == 'group_name_with_slashes':
                # Create role (instructor/staff) groups with format: 'instructor_edX/Course/Run'
                # Since "Group.objects.get_or_create(name=groupnames[1])" would have made group with lowercase name
                # so manually create group name of old type
                if role == CourseInstructorRole:
                    group, __ = Group.objects.get_or_create(
                        name=u'{}_{}'.format('instructor',
                                             course_location.course_id))
                else:
                    group, __ = Group.objects.get_or_create(
                        name=u'{}_{}'.format('staff',
                                             course_location.course_id))
            else:
                # Create role (instructor/staff) groups with format: 'instructor_edx.course.run'
                group, __ = Group.objects.get_or_create(name=groupnames[0])

            if user is not None:
                user.groups.add(group)
        return course

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_get_course_list(self):
        """
        Test getting courses with new access group format e.g. 'instructor_edx.course.run'
        """
        request = self.factory.get('/course')
        request.user = self.user

        course_location = Location(
            ['i4x', 'Org1', 'Course1', 'course', 'Run1'])
        self._create_course_with_access_groups(course_location,
                                               'group_name_with_dots',
                                               self.user)

        # get courses through iterating all courses
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list_by_groups), 1)
        # check both course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

    def test_get_course_list_with_old_group_formats(self):
        """
        Test getting all courses with old course role (instructor/staff) groups
        """
        request = self.factory.get('/course')
        request.user = self.user

        # create a course with new groups name format e.g. 'instructor_edx.course.run'
        course_location = Location(
            ['i4x', 'Org_1', 'Course_1', 'course', 'Run_1'])
        self._create_course_with_access_groups(course_location,
                                               'group_name_with_dots',
                                               self.user)

        # create a course with old groups name format e.g. 'instructor_edX/Course/Run'
        old_course_location = Location(
            ['i4x', 'Org_2', 'Course_2', 'course', 'Run_2'])
        self._create_course_with_access_groups(old_course_location,
                                               'group_name_with_slashes',
                                               self.user)

        # get courses through iterating all courses
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 2)

        # get courses by reversing groups name
        courses_list_by_groups = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list_by_groups), 2)

        # create a new course with older group name format (with dots in names) e.g. 'instructor_edX/Course.name/Run.1'
        old_course_location = Location(
            ['i4x', 'Org.Foo.Bar', 'Course.number', 'course', 'Run.name'])
        self._create_course_with_access_groups(old_course_location,
                                               'group_name_with_slashes',
                                               self.user)
        # get courses through iterating all courses
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 3)
        # get courses by reversing group name formats
        courses_list_by_groups = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list_by_groups), 3)

        # create a new course with older group name format e.g. 'instructor_Run'
        old_course_location = Location(
            ['i4x', 'Org_3', 'Course_3', 'course', 'Run_3'])
        self._create_course_with_access_groups(
            old_course_location, 'group_name_with_course_name_only', self.user)

        # get courses through iterating all courses
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 4)

        # should raise an exception for getting courses with older format of access group by reversing django groups
        with self.assertRaises(ItemNotFoundError):
            courses_list_by_groups = _accessible_courses_list_from_groups(
                request)

    def test_get_course_list_with_invalid_course_location(self):
        """
        Test getting courses with invalid course location (course deleted from modulestore but
        location exists in loc_mapper).
        """
        request = self.factory.get('/course')
        request.user = self.user

        course_location = Location('i4x', 'Org', 'Course', 'course', 'Run')
        self._create_course_with_access_groups(course_location,
                                               'group_name_with_dots',
                                               self.user)

        # get courses through iterating all courses
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list_by_groups), 1)
        # check both course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

        # now delete this course and re-add user to instructor group of this course
        delete_course_and_groups(course_location.course_id, commit=True)

        course_locator = loc_mapper().translate_location(
            course_location.course_id, course_location)
        instructor_group_name = CourseInstructorRole(
            course_locator)._group_names[0]  # pylint: disable=protected-access
        group, __ = Group.objects.get_or_create(name=instructor_group_name)
        self.user.groups.add(group)

        # test that get courses through iterating all courses now returns no course
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 0)

        # now test that get courses by reversing group name formats gives 'ItemNotFoundError'
        with self.assertRaises(ItemNotFoundError):
            _accessible_courses_list_from_groups(request)

    def test_course_listing_performance(self):
        """
        Create large number of courses and give access of some of these courses to the user and
        compare the time to fetch accessible courses for the user through traversing all courses and
        reversing django groups
        """
        # create and log in a non-staff user
        self.user = UserFactory()
        request = self.factory.get('/course')
        request.user = self.user
        self.client.login(username=self.user.username, password='******')

        # create list of random course numbers which will be accessible to the user
        user_course_ids = random.sample(range(TOTAL_COURSES_COUNT),
                                        USER_COURSES_COUNT)

        # create courses and assign those to the user which have their number in user_course_ids
        for number in range(TOTAL_COURSES_COUNT):
            org = 'Org{0}'.format(number)
            course = 'Course{0}'.format(number)
            run = 'Run{0}'.format(number)
            course_location = Location(['i4x', org, course, 'course', run])
            if number in user_course_ids:
                self._create_course_with_access_groups(course_location,
                                                       'group_name_with_dots',
                                                       self.user)
            else:
                self._create_course_with_access_groups(course_location,
                                                       'group_name_with_dots')

        # time the get courses by iterating through all courses
        with Timer() as iteration_over_courses_time_1:
            courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time again the get courses by iterating through all courses
        with Timer() as iteration_over_courses_time_2:
            courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time the get courses by reversing django groups
        with Timer() as iteration_over_groups_time_1:
            courses_list = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time again the get courses by reversing django groups
        with Timer() as iteration_over_groups_time_2:
            courses_list = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # test that the time taken by getting courses through reversing django groups is lower then the time
        # taken by traversing through all courses (if accessible courses are relatively small)
        self.assertGreaterEqual(iteration_over_courses_time_1.elapsed,
                                iteration_over_groups_time_1.elapsed)
        self.assertGreaterEqual(iteration_over_courses_time_2.elapsed,
                                iteration_over_groups_time_2.elapsed)

    def test_get_course_list_with_same_course_id(self):
        """
        Test getting courses with same id but with different name case. Then try to delete one of them and
        check that it is properly deleted and other one is accessible
        """
        # create and log in a non-staff user
        self.user = UserFactory()
        request = self.factory.get('/course')
        request.user = self.user
        self.client.login(username=self.user.username, password='******')

        course_location_caps = Location(
            ['i4x', 'Org', 'COURSE', 'course', 'Run'])
        self._create_course_with_access_groups(course_location_caps,
                                               'group_name_with_dots',
                                               self.user)

        # get courses through iterating all courses
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list_by_groups), 1)
        # check both course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

        # now create another course with same course_id but different name case
        course_location_camel = Location(
            ['i4x', 'Org', 'Course', 'course', 'Run'])
        self._create_course_with_access_groups(course_location_camel,
                                               'group_name_with_dots',
                                               self.user)

        # test that get courses through iterating all courses returns both courses
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 2)

        # test that get courses by reversing group name formats returns only one course
        courses_list_by_groups = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list_by_groups), 1)

        course_locator = loc_mapper().translate_location(
            course_location_caps.course_id, course_location_caps)
        outline_url = course_locator.url_reverse('course/')
        # now delete first course (course_location_caps) and check that it is no longer accessible
        delete_course_and_groups(course_location_caps.course_id, commit=True)
        # add user to this course instructor group since he was removed from that group on course delete
        instructor_group_name = CourseInstructorRole(
            course_locator)._group_names[0]  # pylint: disable=protected-access
        group, __ = Group.objects.get_or_create(name=instructor_group_name)
        self.user.groups.add(group)

        # test viewing the index page which creates missing courses loc_map entries
        resp = self.client.get_html('/course')
        self.assertContains(resp,
                            '<h1 class="page-header">My Courses</h1>',
                            status_code=200,
                            html=True)

        # test that get courses through iterating all courses now returns one course
        courses_list = _accessible_courses_list(request)
        self.assertEqual(len(courses_list), 1)

        # test that get courses by reversing group name formats also returns one course
        courses_list_by_groups = _accessible_courses_list_from_groups(request)
        self.assertEqual(len(courses_list_by_groups), 1)

        # now check that deleted course in not accessible
        response = self.client.get(outline_url, HTTP_ACCEPT='application/json')
        self.assertEqual(response.status_code, 403)

        # now check that other course in accessible
        course_locator = loc_mapper().translate_location(
            course_location_camel.course_id, course_location_camel)
        outline_url = course_locator.url_reverse('course/')
        response = self.client.get(outline_url, HTTP_ACCEPT='application/json')
        self.assertEqual(response.status_code, 200)
Example #45
0
    def setUp(self):
        super(UnitTestLibraries, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username,
                          password=self.user_password)
Example #46
0
class UnitTestLibraries(CourseTestCase):
    """
    Unit tests for library views
    """
    def setUp(self):
        super(UnitTestLibraries, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username,
                          password=self.user_password)

    ######################################################
    # Tests for /library/ - list and create libraries:

    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", False)
    def test_library_creator_status_libraries_not_enabled(self):
        _, nostaff_user = self.create_non_staff_authed_user_client()
        self.assertEqual(get_library_creator_status(nostaff_user), False)

    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
    def test_library_creator_status_with_is_staff_user(self):
        self.assertEqual(get_library_creator_status(self.user), True)

    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
    def test_library_creator_status_with_course_creator_role(self):
        _, nostaff_user = self.create_non_staff_authed_user_client()
        with mock.patch.dict('django.conf.settings.FEATURES',
                             {"ENABLE_CREATOR_GROUP": True}):
            grant_course_creator_status(self.user, nostaff_user)
            self.assertEqual(get_library_creator_status(nostaff_user), True)

    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
    def test_library_creator_status_with_no_course_creator_role(self):
        _, nostaff_user = self.create_non_staff_authed_user_client()
        self.assertEqual(get_library_creator_status(nostaff_user), True)

    @ddt.data((False, False, True), (False, True, False), (True, False, True),
              (True, True, False), (True, None, False), (False, None, True))
    @ddt.unpack
    def test_library_creator_status_settings(self, disable_course,
                                             disable_library, expected_status):
        """
        Ensure that the setting DISABLE_LIBRARY_CREATION overrides DISABLE_COURSE_CREATION as expected.
        """
        _, nostaff_user = self.create_non_staff_authed_user_client()
        with mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True):
            with mock.patch.dict(
                    "django.conf.settings.FEATURES", {
                        "DISABLE_COURSE_CREATION": disable_course,
                        "DISABLE_LIBRARY_CREATION": disable_library
                    }):
                self.assertEqual(get_library_creator_status(nostaff_user),
                                 expected_status)

    @mock.patch.dict('django.conf.settings.FEATURES',
                     {'DISABLE_COURSE_CREATION': True})
    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
    def test_library_creator_status_with_no_course_creator_role_and_disabled_nonstaff_course_creation(
            self):
        """
        Ensure that `DISABLE_COURSE_CREATION` feature works with libraries as well.
        """
        nostaff_client, nostaff_user = self.create_non_staff_authed_user_client(
        )
        self.assertFalse(get_library_creator_status(nostaff_user))

        # To be explicit, this user can GET, but not POST
        get_response = nostaff_client.get_json(LIBRARY_REST_URL)
        post_response = nostaff_client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org',
            'library': 'lib',
            'display_name': "New Library",
        })
        self.assertEqual(get_response.status_code, 200)
        self.assertEqual(post_response.status_code, 403)

    @patch("contentstore.views.library.LIBRARIES_ENABLED", False)
    def test_with_libraries_disabled(self):
        """
        The library URLs should return 404 if libraries are disabled.
        """
        response = self.client.get_json(LIBRARY_REST_URL)
        self.assertEqual(response.status_code, 404)

    def test_list_libraries(self):
        """
        Test that we can GET /library/ to list all libraries visible to the current user.
        """
        # Create some more libraries
        libraries = [LibraryFactory.create() for _ in range(3)]
        lib_dict = dict([(lib.location.library_key, lib) for lib in libraries])

        response = self.client.get_json(LIBRARY_REST_URL)
        self.assertEqual(response.status_code, 200)
        lib_list = parse_json(response)
        self.assertEqual(len(lib_list), len(libraries))
        for entry in lib_list:
            self.assertIn("library_key", entry)
            self.assertIn("display_name", entry)
            key = CourseKey.from_string(entry["library_key"])
            self.assertIn(key, lib_dict)
            self.assertEqual(entry["display_name"], lib_dict[key].display_name)
            del lib_dict[key]  # To ensure no duplicates are matched

    @ddt.data("delete", "put")
    def test_bad_http_verb(self, verb):
        """
        We should get an error if we do weird requests to /library/
        """
        response = getattr(self.client, verb)(LIBRARY_REST_URL)
        self.assertEqual(response.status_code, 405)

    def test_create_library(self):
        """ Create a library. """
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org',
            'library': 'lib',
            'display_name': "New Library",
        })
        self.assertEqual(response.status_code, 200)
        # That's all we check. More detailed tests are in contentstore.tests.test_libraries...

    @patch.dict('django.conf.settings.FEATURES',
                {'ENABLE_CREATOR_GROUP': True})
    def test_lib_create_permission(self):
        """
        Users who are given course creator roles should be able to create libraries.
        """
        self.client.logout()
        ns_user, password = self.create_non_staff_user()
        self.client.login(username=ns_user.username, password=password)
        grant_course_creator_status(self.user, ns_user)
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org',
            'library': 'lib',
            'display_name': "New Library",
        })
        self.assertEqual(response.status_code, 200)

    @patch.dict('django.conf.settings.FEATURES',
                {'ENABLE_CREATOR_GROUP': False})
    def test_lib_create_permission_no_course_creator_role_and_no_course_creator_group(
            self):
        """
        Users who are not given course creator roles should still be able to create libraries
        if ENABLE_CREATOR_GROUP is not enabled.
        """
        self.client.logout()
        ns_user, password = self.create_non_staff_user()
        self.client.login(username=ns_user.username, password=password)
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org',
            'library': 'lib',
            'display_name': "New Library",
        })
        self.assertEqual(response.status_code, 200)

    @patch.dict('django.conf.settings.FEATURES',
                {'ENABLE_CREATOR_GROUP': True})
    def test_lib_create_permission_no_course_creator_role_and_course_creator_group(
            self):
        """
        Users who are not given course creator roles should not be able to create libraries
        if ENABLE_CREATOR_GROUP is enabled.
        """
        self.client.logout()
        ns_user, password = self.create_non_staff_user()
        self.client.login(username=ns_user.username, password=password)
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org',
            'library': 'lib',
            'display_name': "New Library",
        })
        self.assertEqual(response.status_code, 403)

    @ddt.data(
        {},
        {'org': 'org'},
        {'library': 'lib'},
        {
            'org': 'C++',
            'library': 'lib',
            'display_name': 'Lib with invalid characters in key'
        },
        {
            'org': 'Org',
            'library': 'Wh@t?',
            'display_name': 'Lib with invalid characters in key'
        },
    )
    def test_create_library_invalid(self, data):
        """
        Make sure we are prevented from creating libraries with invalid keys/data
        """
        response = self.client.ajax_post(LIBRARY_REST_URL, data)
        self.assertEqual(response.status_code, 400)

    def test_no_duplicate_libraries(self):
        """
        We should not be able to create multiple libraries with the same key
        """
        lib = LibraryFactory.create()
        lib_key = lib.location.library_key
        response = self.client.ajax_post(
            LIBRARY_REST_URL, {
                'org': lib_key.org,
                'library': lib_key.library,
                'display_name': "A Duplicate key, same as 'lib'",
            })
        self.assertIn('already a library defined',
                      parse_json(response)['ErrMsg'])
        self.assertEqual(response.status_code, 400)

    ######################################################
    # Tests for /library/:lib_key/ - get a specific library as JSON or HTML editing view

    def test_get_lib_info(self):
        """
        Test that we can get data about a library (in JSON format) using /library/:key/
        """
        # Create a library
        lib_key = LibraryFactory.create().location.library_key
        # Re-load the library from the modulestore, explicitly including version information:
        lib = self.store.get_library(lib_key,
                                     remove_version=False,
                                     remove_branch=False)
        version = lib.location.library_key.version_guid
        self.assertNotEqual(version, None)

        response = self.client.get_json(make_url_for_lib(lib_key))
        self.assertEqual(response.status_code, 200)
        info = parse_json(response)
        self.assertEqual(info['display_name'], lib.display_name)
        self.assertEqual(info['library_id'], text_type(lib_key))
        self.assertEqual(info['previous_version'], None)
        self.assertNotEqual(info['version'], None)
        self.assertNotEqual(info['version'], '')
        self.assertEqual(info['version'], text_type(version))

    def test_get_lib_edit_html(self):
        """
        Test that we can get the studio view for editing a library using /library/:key/
        """
        lib = LibraryFactory.create()

        response = self.client.get(make_url_for_lib(lib.location.library_key))
        self.assertEqual(response.status_code, 200)
        self.assertIn("<html", response.content)
        self.assertIn(lib.display_name.encode('utf-8'), response.content)

    @ddt.data('library-v1:Nonexistent+library', 'course-v1:Org+Course',
              'course-v1:Org+Course+Run', 'invalid')
    def test_invalid_keys(self, key_str):
        """
        Check that various Nonexistent/invalid keys give 404 errors
        """
        response = self.client.get_json(make_url_for_lib(key_str))
        self.assertEqual(response.status_code, 404)

    def test_bad_http_verb_with_lib_key(self):
        """
        We should get an error if we do weird requests to /library/
        """
        lib = LibraryFactory.create()
        for verb in ("post", "delete", "put"):
            response = getattr(self.client, verb)(make_url_for_lib(
                lib.location.library_key))
            self.assertEqual(response.status_code, 405)

    def test_no_access(self):
        user, password = self.create_non_staff_user()
        self.client.login(username=user, password=password)

        lib = LibraryFactory.create()
        response = self.client.get(make_url_for_lib(lib.location.library_key))
        self.assertEqual(response.status_code, 403)

    def test_get_component_templates(self):
        """
        Verify that templates for adding discussion and advanced components to
        content libraries are not provided.
        """
        lib = LibraryFactory.create()
        lib.advanced_modules = ['lti']
        lib.save()
        templates = [
            template['type']
            for template in get_component_templates(lib, library=True)
        ]
        self.assertIn('problem', templates)
        self.assertNotIn('discussion', templates)
        self.assertNotIn('advanced', templates)

    def test_advanced_problem_types(self):
        """
        Verify that advanced problem types are not provided in problem component for libraries.
        """
        lib = LibraryFactory.create()
        lib.save()

        problem_type_templates = next(
            (component['templates']
             for component in get_component_templates(lib, library=True)
             if component['type'] == 'problem'), [])
        # Each problem template has a category which shows whether problem is a 'problem'
        # or which of the advanced problem type (e.g drag-and-drop-v2).
        problem_type_categories = [
            problem_template['category']
            for problem_template in problem_type_templates
        ]

        for advance_problem_type in settings.ADVANCED_PROBLEM_TYPES:
            self.assertNotIn(advance_problem_type['component'],
                             problem_type_categories)

    def test_manage_library_users(self):
        """
        Simple test that the Library "User Access" view works.
        Also tests that we can use the REST API to assign a user to a library.
        """
        library = LibraryFactory.create()
        extra_user, _ = self.create_non_staff_user()
        manage_users_url = reverse_library_url(
            'manage_library_users', text_type(library.location.library_key))

        response = self.client.get(manage_users_url)
        self.assertEqual(response.status_code, 200)
        # extra_user has not been assigned to the library so should not show up in the list:
        self.assertNotIn(binary_type(extra_user.username), response.content)

        # Now add extra_user to the library:
        user_details_url = reverse_course_url(
            'course_team_handler',
            library.location.library_key,
            kwargs={'email': extra_user.email})
        edit_response = self.client.ajax_post(user_details_url,
                                              {"role": LibraryUserRole.ROLE})
        self.assertIn(edit_response.status_code, (200, 204))

        # Now extra_user should apear in the list:
        response = self.client.get(manage_users_url)
        self.assertEqual(response.status_code, 200)
        self.assertIn(binary_type(extra_user.username), response.content)
Example #47
0
class TestCourseAccess(ModuleStoreTestCase):
    """
    Course-based access (as opposed to access of a non-course xblock)
    """
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        super(TestCourseAccess, self).setUp()
        uname = 'testuser'
        email = '*****@*****.**'
        password = '******'

        # Create the use so we can log them in.
        self.user = User.objects.create_user(uname, email, password)

        # Note that we do not actually need to do anything
        # for registration if we directly mark them active.
        self.user.is_active = True
        # Staff has access to view all courses
        self.user.is_staff = True
        self.user.save()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=uname, password=password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_location = Location(['i4x', 'myu', 'mydept.mycourse', 'course', 'myrun'])
        self.course_locator = loc_mapper().translate_location(
            self.course_location.course_id, self.course_location, False, True
        )
        self.client.ajax_post(
            self.course_locator.url_reverse('course'),
            {
                'org': self.course_location.org, 
                'number': self.course_location.course,
                'display_name': 'My favorite course',
                'run': self.course_location.name,
            }
        )

        self.users = self._create_users()

    def _create_users(self):
        """
        Create 8 users and return them
        """
        users = []
        for i in range(8):
            username = "******".format(i)
            email = "test+user{}@edx.org".format(i)
            user = User.objects.create_user(username, email, 'foo')
            user.is_active = True
            user.save()
            users.append(user)
        return users

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_get_all_users(self):
        """
        Test getting all authors for a course where their permissions run the gamut of allowed group
        types.
        """
        # first check the groupname for the course creator.
        self.assertTrue(
            self.user.groups.filter(
                name="{}_{}".format(INSTRUCTOR_ROLE_NAME, self.course_locator.package_id)
            ).exists(),
            "Didn't add creator as instructor."
        )
        users = copy.copy(self.users)
        user_by_role = {}
        # add the misc users to the course in different groups
        for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
            user_by_role[role] = []
            groupnames, _ = authz.get_all_course_role_groupnames(self.course_locator, role)
            for groupname in groupnames:
                group, _ = Group.objects.get_or_create(name=groupname)
                user = users.pop()
                user_by_role[role].append(user)
                user.groups.add(group)
                user.save()
                self.assertTrue(has_access(user, self.course_locator), "{} does not have access".format(user))
                self.assertTrue(has_access(user, self.course_location), "{} does not have access".format(user))

        response = self.client.get_html(self.course_locator.url_reverse('course_team'))
        for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
            for user in user_by_role[role]:
                self.assertContains(response, user.email)
        
        # test copying course permissions
        copy_course_location = Location(['i4x', 'copyu', 'copydept.mycourse', 'course', 'myrun'])
        copy_course_locator = loc_mapper().translate_location(
            copy_course_location.course_id, copy_course_location, False, True
        )
        # pylint: disable=protected-access
        authz._copy_course_group(self.course_locator, copy_course_locator)
        for role in [INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME]:
            for user in user_by_role[role]:
                self.assertTrue(has_access(user, copy_course_locator), "{} no copy access".format(user))
                self.assertTrue(has_access(user, copy_course_location), "{} no copy access".format(user))
Example #48
0
class TestCourseListing(ModuleStoreTestCase):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.request = self.factory.get('/course')
        self.request.user = self.user
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

    def _create_course_with_access_groups(self,
                                          course_location,
                                          user=None,
                                          store=ModuleStoreEnum.Type.split):
        """
        Create dummy course with 'CourseFactory' and role (instructor/staff) groups
        """
        course = CourseFactory.create(org=course_location.org,
                                      number=course_location.course,
                                      run=course_location.run,
                                      default_store=store)
        self._add_role_access_to_user(user, course.id)
        return course

    def _add_role_access_to_user(self, user, course_id):
        """ Assign access roles to user in the course. """
        if user is not None:
            for role in [CourseInstructorRole, CourseStaffRole]:
                role(course_id).add_users(user)

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_empty_course_listing(self):
        """
        Test on empty course listing, studio name is properly displayed
        """
        message = u"Are you staff on an existing {studio_name} course?".format(
            studio_name=settings.STUDIO_SHORT_NAME)
        response = self.client.get('/home')
        self.assertContains(response, message)

    def test_get_course_list(self):
        """
        Test getting courses with new access group format e.g. 'instructor_edx.course.run'
        """
        course_location = self.store.make_course_key('Org1', 'Course1', 'Run1')
        self._create_course_with_access_groups(course_location, self.user)

        # get courses through iterating all courses
        courses_iter, __ = _accessible_courses_iter_for_tests(self.request)
        courses_list = list(courses_iter)
        self.assertEqual(len(courses_list), 1)

        courses_summary_list, __ = _accessible_courses_summary_iter(
            self.request)
        self.assertEqual(len(list(courses_summary_list)), 1)

        # get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(
            self.request)
        self.assertEqual(len(courses_list_by_groups), 1)

        # check both course lists have same courses
        course_keys_in_course_list = [course.id for course in courses_list]
        course_keys_in_courses_list_by_groups = [
            course.id for course in courses_list_by_groups
        ]

        self.assertEqual(course_keys_in_course_list,
                         course_keys_in_courses_list_by_groups)

    def test_courses_list_with_ccx_courses(self):
        """
        Tests that CCX courses are filtered in course listing.
        """
        # Create a course and assign access roles to user.
        course_location = self.store.make_course_key('Org1', 'Course1', 'Run1')
        course = self._create_course_with_access_groups(
            course_location, self.user)

        # Create a ccx course key and add assign access roles to user.
        ccx_course_key = CCXLocator.from_course_locator(course.id, '1')
        self._add_role_access_to_user(self.user, ccx_course_key)

        # Test that CCX courses are filtered out.
        courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), 1)
        self.assertNotIn(ccx_course_key,
                         [course.id for course in courses_list])

        # Get all courses which user has access.
        instructor_courses = UserBasedRole(
            self.user, CourseInstructorRole.ROLE).courses_with_role()
        staff_courses = UserBasedRole(
            self.user, CourseStaffRole.ROLE).courses_with_role()
        all_courses = (instructor_courses | staff_courses)

        # Verify that CCX course exists in access but filtered by `_accessible_courses_list_from_groups`.
        self.assertIn(ccx_course_key,
                      [access.course_id for access in all_courses])

        # Verify that CCX courses are filtered out while iterating over all courses
        mocked_ccx_course = Mock(id=ccx_course_key)
        with patch(
                'xmodule.modulestore.mixed.MixedModuleStore.get_course_summaries',
                return_value=[mocked_ccx_course]):
            courses_iter, __ = _accessible_courses_iter_for_tests(self.request)
            self.assertEqual(len(list(courses_iter)), 0)

    @ddt.data((ModuleStoreEnum.Type.split, 3), (ModuleStoreEnum.Type.mongo, 2))
    @ddt.unpack
    def test_staff_course_listing(self, default_store, mongo_calls):
        """
        Create courses and verify they take certain amount of mongo calls to call get_courses_accessible_to_user.
        Also verify that fetch accessible courses list for staff user returns CourseSummary instances.
        """

        # Assign & verify staff role to the user
        GlobalStaff().add_users(self.user)
        self.assertTrue(GlobalStaff().has_user(self.user))

        with self.store.default_store(default_store):
            # Create few courses
            for num in range(TOTAL_COURSES_COUNT):
                course_location = self.store.make_course_key(
                    'Org', 'CreatedCourse' + str(num), 'Run')
                self._create_course_with_access_groups(course_location,
                                                       self.user,
                                                       default_store)

        # Fetch accessible courses list & verify their count
        courses_list_by_staff, __ = get_courses_accessible_to_user(
            self.request)
        self.assertEqual(len(list(courses_list_by_staff)), TOTAL_COURSES_COUNT)

        # Verify fetched accessible courses list is a list of CourseSummery instances
        self.assertTrue(
            all(
                isinstance(course, CourseSummary)
                for course in courses_list_by_staff))

        # Now count the db queries for staff
        with check_mongo_calls(mongo_calls):
            list(_accessible_courses_summary_iter(self.request))

    @ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
    def test_get_course_list_with_invalid_course_location(self, store):
        """
        Test getting courses with invalid course location (course deleted from modulestore).
        """
        with self.store.default_store(store):
            course_key = self.store.make_course_key('Org', 'Course', 'Run')
            self._create_course_with_access_groups(course_key, self.user,
                                                   store)

        # get courses through iterating all courses
        courses_iter, __ = _accessible_courses_iter_for_tests(self.request)
        courses_list = list(courses_iter)
        self.assertEqual(len(courses_list), 1)

        courses_summary_iter, __ = _accessible_courses_summary_iter(
            self.request)
        courses_summary_list = list(courses_summary_iter)

        # Verify fetched accessible courses list is a list of CourseSummery instances and only one course
        # is returned
        self.assertTrue(
            all(
                isinstance(course, CourseSummary)
                for course in courses_summary_list))
        self.assertEqual(len(courses_summary_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(
            self.request)
        self.assertEqual(len(courses_list_by_groups), 1)

        course_keys_in_course_list = [course.id for course in courses_list]
        course_keys_in_courses_list_by_groups = [
            course.id for course in courses_list_by_groups
        ]
        # check course lists have same courses
        self.assertEqual(course_keys_in_course_list,
                         course_keys_in_courses_list_by_groups)
        # now delete this course and re-add user to instructor group of this course
        delete_course(course_key, self.user.id)

        CourseInstructorRole(course_key).add_users(self.user)

        # Get courses through iterating all courses
        courses_iter, __ = _accessible_courses_iter_for_tests(self.request)

        # Get course summaries by iterating all courses
        courses_summary_iter, __ = _accessible_courses_summary_iter(
            self.request)

        # Get courses by reversing group name formats
        courses_list_by_groups, __ = _accessible_courses_list_from_groups(
            self.request)

        # Test that course list returns no course
        self.assertEqual([
            len(list(courses_iter)),
            len(courses_list_by_groups),
            len(list(courses_summary_iter))
        ], [0, 0, 0])

    @ddt.data((ModuleStoreEnum.Type.split, 3, 3),
              (ModuleStoreEnum.Type.mongo, 2, 2))
    @ddt.unpack
    def test_course_listing_performance(self, store,
                                        courses_list_from_group_calls,
                                        courses_list_calls):
        """
        Create large number of courses and give access of some of these courses to the user and
        compare the time to fetch accessible courses for the user through traversing all courses and
        reversing django groups
        """
        # create list of random course numbers which will be accessible to the user
        user_course_ids = random.sample(list(range(TOTAL_COURSES_COUNT)),
                                        USER_COURSES_COUNT)

        # create courses and assign those to the user which have their number in user_course_ids
        with self.store.default_store(store):
            for number in range(TOTAL_COURSES_COUNT):
                org = 'Org{0}'.format(number)
                course = 'Course{0}'.format(number)
                run = 'Run{0}'.format(number)
                course_location = self.store.make_course_key(org, course, run)
                if number in user_course_ids:
                    self._create_course_with_access_groups(course_location,
                                                           self.user,
                                                           store=store)
                else:
                    self._create_course_with_access_groups(course_location,
                                                           store=store)

        # get courses by iterating through all courses
        courses_iter, __ = _accessible_courses_iter_for_tests(self.request)
        self.assertEqual(len(list(courses_iter)), USER_COURSES_COUNT)

        # again get courses by iterating through all courses
        courses_iter, __ = _accessible_courses_iter_for_tests(self.request)
        self.assertEqual(len(list(courses_iter)), USER_COURSES_COUNT)

        # get courses by reversing django groups
        courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # again get courses by reversing django groups
        courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # Now count the db queries
        with check_mongo_calls(courses_list_from_group_calls):
            _accessible_courses_list_from_groups(self.request)

        with check_mongo_calls(courses_list_calls):
            list(_accessible_courses_iter_for_tests(self.request))
        # Calls:
        #    1) query old mongo
        #    2) get_more on old mongo
        #    3) query split (but no courses so no fetching of data)

    @ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
    def test_course_listing_errored_deleted_courses(self, store):
        """
        Create good courses, courses that won't load, and deleted courses which still have
        roles. Test course listing.
        """
        with self.store.default_store(store):
            course_location = self.store.make_course_key(
                'testOrg', 'testCourse', 'RunBabyRun')
            self._create_course_with_access_groups(course_location, self.user,
                                                   store)

            course_location = self.store.make_course_key(
                'testOrg', 'doomedCourse', 'RunBabyRun')
            self._create_course_with_access_groups(course_location, self.user,
                                                   store)
            self.store.delete_course(course_location, self.user.id)

        courses_list, __ = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), 1, courses_list)

    @ddt.data(OrgStaffRole('AwesomeOrg'), OrgInstructorRole('AwesomeOrg'))
    def test_course_listing_org_permissions(self, role):
        """
        Create multiple courses within the same org.  Verify that someone with org-wide permissions can access
        all of them.
        """
        org_course_one = self.store.make_course_key('AwesomeOrg', 'Course1',
                                                    'RunBabyRun')
        CourseFactory.create(org=org_course_one.org,
                             number=org_course_one.course,
                             run=org_course_one.run)

        org_course_two = self.store.make_course_key('AwesomeOrg', 'Course2',
                                                    'RunBabyRun')
        CourseFactory.create(org=org_course_two.org,
                             number=org_course_two.course,
                             run=org_course_two.run)

        # Two types of org-wide roles have edit permissions: staff and instructor.  We test both
        role.add_users(self.user)

        with self.assertRaises(AccessListFallback):
            _accessible_courses_list_from_groups(self.request)
        courses_list, __ = get_courses_accessible_to_user(self.request)

        # Verify fetched accessible courses list is a list of CourseSummery instances and test expacted
        # course count is returned
        self.assertEqual(len(list(courses_list)), 2)
        self.assertTrue(
            all(isinstance(course, CourseSummary) for course in courses_list))

    def test_course_listing_with_actions_in_progress(self):
        sourse_course_key = CourseLocator('source-Org', 'source-Course',
                                          'source-Run')

        num_courses_to_create = 3
        courses = [
            self._create_course_with_access_groups(
                CourseLocator('Org', 'CreatedCourse' + str(num), 'Run'),
                self.user,
            ) for num in range(num_courses_to_create)
        ]
        courses_in_progress = [
            self._create_course_with_access_groups(
                CourseLocator('Org', 'InProgressCourse' + str(num), 'Run'),
                self.user,
            ) for num in range(num_courses_to_create)
        ]

        # simulate initiation of course actions
        for course in courses_in_progress:
            CourseRerunState.objects.initiated(
                sourse_course_key,
                destination_course_key=course.id,
                user=self.user,
                display_name="test course")

        # verify return values
        def _set_of_course_keys(course_list, key_attribute_name='id'):
            """Returns a python set of course keys by accessing the key with the given attribute name."""
            return set(getattr(c, key_attribute_name) for c in course_list)

        found_courses, unsucceeded_course_actions = _accessible_courses_iter_for_tests(
            self.request)
        self.assertSetEqual(_set_of_course_keys(courses + courses_in_progress),
                            _set_of_course_keys(found_courses))
        self.assertSetEqual(
            _set_of_course_keys(courses_in_progress),
            _set_of_course_keys(unsucceeded_course_actions, 'course_key'))
Example #49
0
class TestUsersDefaultRole(ModuleStoreTestCase):
    """
    Unit tests for checking enrollment and default forum role "Student" of a logged in user
    """
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestUsersDefaultRole, self).setUp()
        # create and log in a staff user.
        self.user = UserFactory(is_staff=True)  # pylint: disable=no-member
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

        # create a course via the view handler to create course
        self.course_location = Location(['i4x', 'Org_1', 'Course_1', 'course', 'Run_1'])
        self._create_course_with_given_location(self.course_location)

    def _create_course_with_given_location(self, course_location):
        """
        Create course at provided location
        """
        course_locator = loc_mapper().translate_location(
            course_location.course_id, course_location, False, True
        )
        resp = self.client.ajax_post(
            course_locator.url_reverse('course'),
            {
                'org': course_location.org,
                'number': course_location.course,
                'display_name': 'test course',
                'run': course_location.name,
            }
        )
        return resp

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        super(TestUsersDefaultRole, self).tearDown()

    def test_user_forum_default_role_on_course_deletion(self):
        """
        Test that a user enrolls and gets "Student" forum role for that course which he creates and remains
        enrolled even the course is deleted and keeps its "Student" forum role for that course
        """
        course_id = self.course_location.course_id
        # check that user has enrollment for this course
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_id))

        # check that user has his default "Student" forum role for this course
        self.assertTrue(self.user.roles.filter(name="Student", course_id=course_id))  # pylint: disable=no-member

        delete_course_and_groups(course_id, commit=True)

        # check that user's enrollment for this course is not deleted
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_id))

        # check that user has forum role for this course even after deleting it
        self.assertTrue(self.user.roles.filter(name="Student", course_id=course_id))  # pylint: disable=no-member

    def test_user_role_on_course_recreate(self):
        """
        Test that creating same course again after deleting it gives user his default
        forum role "Student" for that course
        """
        course_id = self.course_location.course_id
        # check that user has enrollment and his default "Student" forum role for this course
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_id))
        self.assertTrue(self.user.roles.filter(name="Student", course_id=course_id))  # pylint: disable=no-member

        # delete this course and recreate this course with same user
        delete_course_and_groups(course_id, commit=True)
        resp = self._create_course_with_given_location(self.course_location)
        self.assertEqual(resp.status_code, 200)

        # check that user has his enrollment for this course
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_id))

        # check that user has his default "Student" forum role for this course
        self.assertTrue(self.user.roles.filter(name="Student", course_id=course_id))  # pylint: disable=no-member

    def test_user_role_on_course_recreate_with_change_name_case(self):
        """
        Test that creating same course again with different name case after deleting it gives user
        his default forum role "Student" for that course
        """
        course_location = self.course_location
        # check that user has enrollment and his default "Student" forum role for this course
        self.assertTrue(CourseEnrollment.is_enrolled(self.user, course_location.course_id))
        # delete this course and recreate this course with same user
        delete_course_and_groups(course_location.course_id, commit=True)

        # now create same course with different name case ('uppercase')
        new_course_location = Location(
            ['i4x', course_location.org, course_location.course.upper(), 'course', course_location.name]
        )
        resp = self._create_course_with_given_location(new_course_location)
        self.assertEqual(resp.status_code, 200)

        # check that user has his default "Student" forum role again for this course (with changed name case)
        self.assertTrue(
            self.user.roles.filter(name="Student", course_id=new_course_location.course_id)  # pylint: disable=no-member
        )
Example #50
0
class TestCourseListing(ModuleStoreTestCase):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.request = self.factory.get('/course')
        self.request.user = self.user
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')

    def _create_course_with_access_groups(self, course_location, user=None):
        """
        Create dummy course with 'CourseFactory' and role (instructor/staff) groups
        """
        course = CourseFactory.create(
            org=course_location.org,
            number=course_location.course,
            run=course_location.run
        )

        if user is not None:
            for role in [CourseInstructorRole, CourseStaffRole]:
                role(course.id).add_users(user)

        return course

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_get_course_list(self):
        """
        Test getting courses with new access group format e.g. 'instructor_edx.course.run'
        """
        course_location = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1')
        self._create_course_with_access_groups(course_location, self.user)

        # get courses through iterating all courses
        courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 1)
        # check both course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

    def test_errored_course_global_staff(self):
        """
        Test the course list for global staff when get_course returns an ErrorDescriptor
        """
        GlobalStaff().add_users(self.user)

        course_key = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1')
        self._create_course_with_access_groups(course_key, self.user)

        with patch('xmodule.modulestore.mongo.base.MongoKeyValueStore', Mock(side_effect=Exception)):
            self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor)

            # get courses through iterating all courses
            courses_list = _accessible_courses_list(self.request)
            self.assertEqual(courses_list, [])

            # get courses by reversing group name formats
            courses_list_by_groups = _accessible_courses_list_from_groups(self.request)
            self.assertEqual(courses_list_by_groups, [])

    def test_errored_course_regular_access(self):
        """
        Test the course list for regular staff when get_course returns an ErrorDescriptor
        """
        GlobalStaff().remove_users(self.user)
        CourseStaffRole(SlashSeparatedCourseKey('Non', 'Existent', 'Course')).add_users(self.user)

        course_key = SlashSeparatedCourseKey('Org1', 'Course1', 'Run1')
        self._create_course_with_access_groups(course_key, self.user)

        with patch('xmodule.modulestore.mongo.base.MongoKeyValueStore', Mock(side_effect=Exception)):
            self.assertIsInstance(modulestore().get_course(course_key), ErrorDescriptor)

            # get courses through iterating all courses
            courses_list = _accessible_courses_list(self.request)
            self.assertEqual(courses_list, [])

            # get courses by reversing group name formats
            courses_list_by_groups = _accessible_courses_list_from_groups(self.request)
            self.assertEqual(courses_list_by_groups, [])
            self.assertEqual(courses_list, courses_list_by_groups)

    def test_get_course_list_with_invalid_course_location(self):
        """
        Test getting courses with invalid course location (course deleted from modulestore).
        """
        course_key = SlashSeparatedCourseKey('Org', 'Course', 'Run')
        self._create_course_with_access_groups(course_key, self.user)

        # get courses through iterating all courses
        courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 1)
        # check both course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

        # now delete this course and re-add user to instructor group of this course
        delete_course_and_groups(course_key, commit=True)

        CourseInstructorRole(course_key).add_users(self.user)

        # test that get courses through iterating all courses now returns no course
        courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 0)

    def test_course_listing_performance(self):
        """
        Create large number of courses and give access of some of these courses to the user and
        compare the time to fetch accessible courses for the user through traversing all courses and
        reversing django groups
        """
        # create list of random course numbers which will be accessible to the user
        user_course_ids = random.sample(range(TOTAL_COURSES_COUNT), USER_COURSES_COUNT)

        # create courses and assign those to the user which have their number in user_course_ids
        for number in range(TOTAL_COURSES_COUNT):
            org = 'Org{0}'.format(number)
            course = 'Course{0}'.format(number)
            run = 'Run{0}'.format(number)
            course_location = SlashSeparatedCourseKey(org, course, run)
            if number in user_course_ids:
                self._create_course_with_access_groups(course_location, self.user)
            else:
                self._create_course_with_access_groups(course_location)

        # time the get courses by iterating through all courses
        with Timer() as iteration_over_courses_time_1:
            courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time again the get courses by iterating through all courses
        with Timer() as iteration_over_courses_time_2:
            courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time the get courses by reversing django groups
        with Timer() as iteration_over_groups_time_1:
            courses_list = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # time again the get courses by reversing django groups
        with Timer() as iteration_over_groups_time_2:
            courses_list = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), USER_COURSES_COUNT)

        # test that the time taken by getting courses through reversing django groups is lower then the time
        # taken by traversing through all courses (if accessible courses are relatively small)
        self.assertGreaterEqual(iteration_over_courses_time_1.elapsed, iteration_over_groups_time_1.elapsed)
        self.assertGreaterEqual(iteration_over_courses_time_2.elapsed, iteration_over_groups_time_2.elapsed)

        # Now count the db queries
        store = modulestore()._get_modulestore_by_type(MONGO_MODULESTORE_TYPE)
        with check_mongo_calls(store.collection, USER_COURSES_COUNT):
            courses_list = _accessible_courses_list_from_groups(self.request)

        with check_mongo_calls(store.collection, 1):
            courses_list = _accessible_courses_list(self.request)

    def test_get_course_list_with_same_course_id(self):
        """
        Test getting courses with same id but with different name case. Then try to delete one of them and
        check that it is properly deleted and other one is accessible
        """
        course_location_caps = SlashSeparatedCourseKey('Org', 'COURSE', 'Run')
        self._create_course_with_access_groups(course_location_caps, self.user)

        # get courses through iterating all courses
        courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 1)

        # get courses by reversing group name formats
        courses_list_by_groups = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 1)
        # check both course lists have same courses
        self.assertEqual(courses_list, courses_list_by_groups)

        # now create another course with same course_id but different name case
        course_location_camel = SlashSeparatedCourseKey('Org', 'Course', 'Run')
        self._create_course_with_access_groups(course_location_camel, self.user)

        # test that get courses through iterating all courses returns both courses
        courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 2)

        # test that get courses by reversing group name formats returns both courses
        courses_list_by_groups = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 2)

        # now delete first course (course_location_caps) and check that it is no longer accessible
        delete_course_and_groups(course_location_caps, commit=True)

        # test that get courses through iterating all courses now returns one course
        courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 1)

        # test that get courses by reversing group name formats also returns one course
        courses_list_by_groups = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list_by_groups), 1)

        # now check that deleted course is not accessible
        outline_url = reverse_course_url('course_handler', course_location_caps)
        response = self.client.get(outline_url, HTTP_ACCEPT='application/json')
        self.assertEqual(response.status_code, 403)

        # now check that other course is accessible
        outline_url = reverse_course_url('course_handler', course_location_camel)
        response = self.client.get(outline_url, HTTP_ACCEPT='application/json')
        self.assertEqual(response.status_code, 200)

    def test_course_listing_errored_deleted_courses(self):
        """
        Create good courses, courses that won't load, and deleted courses which still have
        roles. Test course listing.
        """
        store = modulestore()._get_modulestore_by_type(MONGO_MODULESTORE_TYPE)

        course_location = SlashSeparatedCourseKey('testOrg', 'testCourse', 'RunBabyRun')
        self._create_course_with_access_groups(course_location, self.user)

        course_location = SlashSeparatedCourseKey('testOrg', 'doomedCourse', 'RunBabyRun')
        self._create_course_with_access_groups(course_location, self.user)
        store.delete_course(course_location)

        course_location = SlashSeparatedCourseKey('testOrg', 'erroredCourse', 'RunBabyRun')
        course = self._create_course_with_access_groups(course_location, self.user)
        course_db_record = store._find_one(course.location)
        course_db_record.setdefault('metadata', {}).get('tabs', []).append({"type": "wiko", "name": "Wiki" })
        store.collection.update(
            {'_id': course.location.to_deprecated_son()},
            {'$set': {
                'metadata.tabs': course_db_record['metadata']['tabs'],
            }},
        )

        courses_list = _accessible_courses_list_from_groups(self.request)
        self.assertEqual(len(courses_list), 1, courses_list)

    @ddt.data(OrgStaffRole('AwesomeOrg'), OrgInstructorRole('AwesomeOrg'))
    def test_course_listing_org_permissions(self, role):
        """
        Create multiple courses within the same org.  Verify that someone with org-wide permissions can access
        all of them.
        """
        org_course_one = SlashSeparatedCourseKey('AwesomeOrg', 'Course1', 'RunBabyRun')
        CourseFactory.create(
            org=org_course_one.org,
            number=org_course_one.course,
            run=org_course_one.run
        )

        org_course_two = SlashSeparatedCourseKey('AwesomeOrg', 'Course2', 'RunRunRun')
        CourseFactory.create(
            org=org_course_two.org,
            number=org_course_two.course,
            run=org_course_two.run
        )

        # Two types of org-wide roles have edit permissions: staff and instructor.  We test both
        role.add_users(self.user)

        with self.assertRaises(AccessListFallback):
            _accessible_courses_list_from_groups(self.request)
        courses_list = _accessible_courses_list(self.request)
        self.assertEqual(len(courses_list), 2)
Example #51
0
    def setUp(self):
        super(UnitTestLibraries, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=self.user_password)
Example #52
0
class TestCourseAccess(ModuleStoreTestCase):
    """
    Course-based access (as opposed to access of a non-course xblock)
    """
    def setUp(self):
        """
        Create a staff user and log them in (creating the client).

        Create a pool of users w/o granting them any permissions
        """
        user_password = super(TestCourseAccess, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=user_password)

        # create a course via the view handler which has a different strategy for permissions than the factory
        self.course_key = SlashSeparatedCourseKey('myu', 'mydept.mycourse', 'myrun')
        course_url = reverse_url('course_handler')
        self.client.ajax_post(
            course_url,
            {
                'org': self.course_key.org,
                'number': self.course_key.course,
                'display_name': 'My favorite course',
                'run': self.course_key.run,
            },
        )

        self.users = self._create_users()

    def _create_users(self):
        """
        Create 8 users and return them
        """
        users = []
        for i in range(8):
            username = "******".format(i)
            email = "test+user{}@edx.org".format(i)
            user = User.objects.create_user(username, email, 'foo')
            user.is_active = True
            user.save()
            users.append(user)
        return users

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_get_all_users(self):
        """
        Test getting all authors for a course where their permissions run the gamut of allowed group
        types.
        """
        # first check the course creator.has explicit access (don't use has_access as is_staff
        # will trump the actual test)
        self.assertTrue(
            CourseInstructorRole(self.course_key).has_user(self.user),
            "Didn't add creator as instructor."
        )
        users = copy.copy(self.users)
        # doesn't use role.users_with_role b/c it's verifying the roles.py behavior
        user_by_role = {}
        # add the misc users to the course in different groups
        for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]:
            user_by_role[role] = []
            # Org-based roles are created via org name, rather than course_key
            if (role is OrgStaffRole) or (role is OrgInstructorRole):
                group = role(self.course_key.org)
            else:
                group = role(self.course_key)
            # NOTE: this loop breaks the roles.py abstraction by purposely assigning
            # users to one of each possible groupname in order to test that has_course_author_access
            # and remove_user work
            user = users.pop()
            group.add_users(user)
            user_by_role[role].append(user)
            self.assertTrue(auth.has_course_author_access(user, self.course_key), "{} does not have access".format(user))

        course_team_url = reverse_course_url('course_team_handler', self.course_key)
        response = self.client.get_html(course_team_url)
        for role in [CourseInstructorRole, CourseStaffRole]:  # Global and org-based roles don't appear on this page
            for user in user_by_role[role]:
                self.assertContains(response, user.email)

        # test copying course permissions
        copy_course_key = SlashSeparatedCourseKey('copyu', 'copydept.mycourse', 'myrun')
        for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]:
            if (role is OrgStaffRole) or (role is OrgInstructorRole):
                auth.add_users(
                    self.user,
                    role(copy_course_key.org),
                    *role(self.course_key.org).users_with_role()
                )
            else:
                auth.add_users(
                    self.user,
                    role(copy_course_key),
                    *role(self.course_key).users_with_role()
                )
        # verify access in copy course and verify that removal from source course w/ the various
        # groupnames works
        for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]:
            for user in user_by_role[role]:
                # forcefully decache the groups: premise is that any real request will not have
                # multiple objects repr the same user but this test somehow uses different instance
                # in above add_users call
                if hasattr(user, '_roles'):
                    del user._roles

                self.assertTrue(auth.has_course_author_access(user, copy_course_key), "{} no copy access".format(user))
                if (role is OrgStaffRole) or (role is OrgInstructorRole):
                    auth.remove_users(self.user, role(self.course_key.org), user)
                else:
                    auth.remove_users(self.user, role(self.course_key), user)
                self.assertFalse(auth.has_course_author_access(user, self.course_key), "{} remove didn't work".format(user))
Example #53
0
class AuthTestCase(ContentStoreTestCase):
    """Check that various permissions-related things work"""

    CREATE_USER = False
    ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']

    def setUp(self):
        super(AuthTestCase, self).setUp()

        self.email = '*****@*****.**'
        self.pw = 'xyz'
        self.username = '******'
        self.client = AjaxEnabledTestClient()
        # clear the cache so ratelimiting won't affect these tests
        cache.clear()

    def check_page_get(self, url, expected):
        resp = self.client.get_html(url)
        self.assertEqual(resp.status_code, expected)
        return resp

    def test_public_pages_load(self):
        """Make sure pages that don't require login load without error."""
        pages = (
            reverse('login'),
            reverse('signup'),
        )
        for page in pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, 200)

    def test_create_account_errors(self):
        # No post data -- should fail
        registration_url = reverse('user_api_registration')
        resp = self.client.post(registration_url, {})
        self.assertEqual(resp.status_code, 400)

    def test_create_account(self):
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)

    def test_create_account_username_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account(self.username, "*****@*****.**", "password")
        # we have a constraint on unique usernames, so this should fail
        self.assertEqual(resp.status_code, 409)

    def test_create_account_pw_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account("abcdef", "*****@*****.**", self.pw)
        # we can have two users with the same password, so this should succeed
        self.assertEqual(resp.status_code, 200)

    def test_login(self):
        self.create_account(self.username, self.email, self.pw)

        # Not activated yet.  Login should fail.
        resp = self._login(self.email, self.pw)

        self.activate_user(self.email)

        # Now login should work
        self.login(self.email, self.pw)

    def test_login_ratelimited(self):
        # try logging in 30 times, the default limit in the number of failed
        # login attempts in one 5 minute period before the rate gets limited
        for i in xrange(30):
            resp = self._login(self.email, 'wrong_password{0}'.format(i))
            self.assertEqual(resp.status_code, 403)
        resp = self._login(self.email, 'wrong_password')
        self.assertEqual(resp.status_code, 403)
        self.assertIn('Too many failed login attempts.', resp.content)

    @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3)
    @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2)
    def test_excessive_login_failures(self):
        # try logging in 3 times, the account should get locked for 3 seconds
        # note we want to keep the lockout time short, so we don't slow down the tests

        with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}):
            self.create_account(self.username, self.email, self.pw)
            self.activate_user(self.email)

            for i in xrange(3):
                resp = self._login(self.email, 'wrong_password{0}'.format(i))
                self.assertEqual(resp.status_code, 403)
                self.assertIn(
                    'Email or password is incorrect.',
                    resp.content
                )

            # now the account should be locked

            resp = self._login(self.email, 'wrong_password')
            self.assertEqual(resp.status_code, 403)
            self.assertIn(
                'This account has been temporarily locked due to excessive login failures.',
                resp.content
            )

            with freeze_time('2100-01-01'):
                self.login(self.email, self.pw)

            # make sure the failed attempt counter gets reset on successful login
            resp = self._login(self.email, 'wrong_password')
            self.assertEqual(resp.status_code, 403)
            self.assertIn(
                'Email or password is incorrect.',
                resp.content
            )

            # account should not be locked out after just one attempt
            self.login(self.email, self.pw)

            # do one more login when there is no bad login counter row at all in the database to
            # test the "ObjectNotFound" case
            self.login(self.email, self.pw)

    def test_login_link_on_activation_age(self):
        self.create_account(self.username, self.email, self.pw)
        # we want to test the rendering of the activation page when the user isn't logged in
        self.client.logout()
        resp = self._activate_user(self.email)
        self.assertEqual(resp.status_code, 200)

        # check the the HTML has links to the right login page. Note that this is merely a content
        # check and thus could be fragile should the wording change on this page
        expected = 'You can now <a href="' + reverse('login') + '">sign in</a>.'
        self.assertIn(expected, resp.content.decode('utf-8'))

    def test_private_pages_auth(self):
        """Make sure pages that do require login work."""
        auth_pages = (
            '/home/',
        )

        # These are pages that should just load when the user is logged in
        # (no data needed)
        simple_auth_pages = (
            '/home/',
        )

        # need an activated user
        self.test_create_account()

        # Create a new session
        self.client = AjaxEnabledTestClient()

        # Not logged in.  Should redirect to login.
        print('Not logged in')
        for page in auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=302)

        # Logged in should work.
        self.login(self.email, self.pw)

        print('Logged in')
        for page in simple_auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=200)

    def test_index_auth(self):

        # not logged in.  Should return a redirect.
        resp = self.client.get_html('/home/')
        self.assertEqual(resp.status_code, 302)

        # Logged in should work.

    @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1)
    def test_inactive_session_timeout(self):
        """
        Verify that an inactive session times out and redirects to the
        login page
        """
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)

        self.login(self.email, self.pw)

        # make sure we can access courseware immediately
        course_url = '/home/'
        resp = self.client.get_html(course_url)
        self.assertEquals(resp.status_code, 200)

        # then wait a bit and see if we get timed out
        time.sleep(2)

        resp = self.client.get_html(course_url)

        # re-request, and we should get a redirect to login page
        self.assertRedirects(resp, settings.LOGIN_URL + '?next=/home/')

    @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
    def test_signup_button_index_page(self):
        """
        Navigate to the home page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
        is turned off
        """
        response = self.client.get(reverse('homepage'))
        self.assertNotIn('<a class="action action-signup" href="/signup">Sign Up</a>', response.content)

    @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
    def test_signup_button_login_page(self):
        """
        Navigate to the login page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
        is turned off
        """
        response = self.client.get(reverse('login'))
        self.assertNotIn('<a class="action action-signup" href="/signup">Sign Up</a>', response.content)

    @mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
    def test_signup_link_login_page(self):
        """
        Navigate to the login page and check the Sign Up link is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
        is turned off
        """
        response = self.client.get(reverse('login'))
        self.assertNotIn('<a href="/signup" class="action action-signin">Don&#39;t have a Studio Account? Sign up!</a>',
                         response.content)
Example #54
0
class InternationalizationTest(ModuleStoreTestCase):
    """
    Tests to validate Internationalization.
    """
    def setUp(self):
        """
        These tests need a user in the DB so that the django Test Client
        can log them in.
        They inherit from the ModuleStoreTestCase class so that the mongodb collection
        will be cleared out before each test case execution and deleted
        afterwards.
        """
        super(InternationalizationTest, self).setUp(create_user=False)

        self.uname = 'testuser'
        self.email = '*****@*****.**'
        self.password = '******'

        # Create the use so we can log them in.
        self.user = User.objects.create_user(self.uname, self.email,
                                             self.password)

        # Note that we do not actually need to do anything
        # for registration if we directly mark them active.
        self.user.is_active = True
        # Staff has access to view all courses
        self.user.is_staff = True
        self.user.save()

        self.course_data = {
            'org': 'MITx',
            'number': '999',
            'display_name': 'Robot Super Course',
        }

    def test_course_plain_english(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html('/home/')
        self.assertContains(resp,
                            '<h1 class="page-header">Studio Home</h1>',
                            status_code=200,
                            html=True)

    def test_course_explicit_english(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html(
            '/home/',
            {},
            HTTP_ACCEPT_LANGUAGE='en',
        )

        self.assertContains(resp,
                            '<h1 class="page-header">Studio Home</h1>',
                            status_code=200,
                            html=True)

    # ****
    # NOTE:
    # ****
    #
    # This test will break when we replace this fake 'test' language
    # with actual Esperanto. This test will need to be updated with
    # actual Esperanto at that time.
    # Test temporarily disable since it depends on creation of dummy strings
    @skip
    def test_course_with_accents(self):
        """Test viewing the index page with no courses"""
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.uname, password=self.password)

        resp = self.client.get_html('/home/', {}, HTTP_ACCEPT_LANGUAGE='eo')

        TEST_STRING = (u'<h1 class="title-1">'
                       u'My \xc7\xf6\xfcrs\xe9s L#'
                       u'</h1>')

        self.assertContains(resp, TEST_STRING, status_code=200, html=True)
Example #55
0
class UnitTestLibraries(CourseTestCase):
    """
    Unit tests for library views
    """

    def setUp(self):
        super(UnitTestLibraries, self).setUp()

        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password=self.user_password)

    ######################################################
    # Tests for /library/ - list and create libraries:

    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", False)
    def test_library_creator_status_libraries_not_enabled(self):
        _, nostaff_user = self.create_non_staff_authed_user_client()
        self.assertEqual(get_library_creator_status(nostaff_user), False)

    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
    def test_library_creator_status_with_is_staff_user(self):
        self.assertEqual(get_library_creator_status(self.user), True)

    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
    def test_library_creator_status_with_course_creator_role(self):
        _, nostaff_user = self.create_non_staff_authed_user_client()
        with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
            grant_course_creator_status(self.user, nostaff_user)
            self.assertEqual(get_library_creator_status(nostaff_user), True)

    @mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
    def test_library_creator_status_with_no_course_creator_role(self):
        _, nostaff_user = self.create_non_staff_authed_user_client()
        self.assertEqual(get_library_creator_status(nostaff_user), True)

    @patch("contentstore.views.library.LIBRARIES_ENABLED", False)
    def test_with_libraries_disabled(self):
        """
        The library URLs should return 404 if libraries are disabled.
        """
        response = self.client.get_json(LIBRARY_REST_URL)
        self.assertEqual(response.status_code, 404)

    def test_list_libraries(self):
        """
        Test that we can GET /library/ to list all libraries visible to the current user.
        """
        # Create some more libraries
        libraries = [LibraryFactory.create() for _ in range(3)]
        lib_dict = dict([(lib.location.library_key, lib) for lib in libraries])

        response = self.client.get_json(LIBRARY_REST_URL)
        self.assertEqual(response.status_code, 200)
        lib_list = parse_json(response)
        self.assertEqual(len(lib_list), len(libraries))
        for entry in lib_list:
            self.assertIn("library_key", entry)
            self.assertIn("display_name", entry)
            key = CourseKey.from_string(entry["library_key"])
            self.assertIn(key, lib_dict)
            self.assertEqual(entry["display_name"], lib_dict[key].display_name)
            del lib_dict[key]  # To ensure no duplicates are matched

    @ddt.data("delete", "put")
    def test_bad_http_verb(self, verb):
        """
        We should get an error if we do weird requests to /library/
        """
        response = getattr(self.client, verb)(LIBRARY_REST_URL)
        self.assertEqual(response.status_code, 405)

    def test_create_library(self):
        """ Create a library. """
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org',
            'library': 'lib',
            'display_name': "New Library",
        })
        self.assertEqual(response.status_code, 200)
        # That's all we check. More detailed tests are in contentstore.tests.test_libraries...

    @patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True})
    def test_lib_create_permission(self):
        """
        Users who are given course creator roles should be able to create libraries.
        """
        self.client.logout()
        ns_user, password = self.create_non_staff_user()
        self.client.login(username=ns_user.username, password=password)
        grant_course_creator_status(self.user, ns_user)
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org', 'library': 'lib', 'display_name': "New Library",
        })
        self.assertEqual(response.status_code, 200)

    @patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': False})
    def test_lib_create_permission_no_course_creator_role_and_no_course_creator_group(self):
        """
        Users who are not given course creator roles should still be able to create libraries
        if ENABLE_CREATOR_GROUP is not enabled.
        """
        self.client.logout()
        ns_user, password = self.create_non_staff_user()
        self.client.login(username=ns_user.username, password=password)
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org', 'library': 'lib', 'display_name': "New Library",
        })
        self.assertEqual(response.status_code, 200)

    @patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True})
    def test_lib_create_permission_no_course_creator_role_and_course_creator_group(self):
        """
        Users who are not given course creator roles should not be able to create libraries
        if ENABLE_CREATOR_GROUP is enabled.
        """
        self.client.logout()
        ns_user, password = self.create_non_staff_user()
        self.client.login(username=ns_user.username, password=password)
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': 'org', 'library': 'lib', 'display_name': "New Library",
        })
        self.assertEqual(response.status_code, 403)

    @ddt.data(
        {},
        {'org': 'org'},
        {'library': 'lib'},
        {'org': 'C++', 'library': 'lib', 'display_name': 'Lib with invalid characters in key'},
        {'org': 'Org', 'library': 'Wh@t?', 'display_name': 'Lib with invalid characters in key'},
    )
    def test_create_library_invalid(self, data):
        """
        Make sure we are prevented from creating libraries with invalid keys/data
        """
        response = self.client.ajax_post(LIBRARY_REST_URL, data)
        self.assertEqual(response.status_code, 400)

    def test_no_duplicate_libraries(self):
        """
        We should not be able to create multiple libraries with the same key
        """
        lib = LibraryFactory.create()
        lib_key = lib.location.library_key
        response = self.client.ajax_post(LIBRARY_REST_URL, {
            'org': lib_key.org,
            'library': lib_key.library,
            'display_name': "A Duplicate key, same as 'lib'",
        })
        self.assertIn('already a library defined', parse_json(response)['ErrMsg'])
        self.assertEqual(response.status_code, 400)

    ######################################################
    # Tests for /library/:lib_key/ - get a specific library as JSON or HTML editing view

    def test_get_lib_info(self):
        """
        Test that we can get data about a library (in JSON format) using /library/:key/
        """
        # Create a library
        lib_key = LibraryFactory.create().location.library_key
        # Re-load the library from the modulestore, explicitly including version information:
        lib = self.store.get_library(lib_key, remove_version=False, remove_branch=False)
        version = lib.location.library_key.version_guid
        self.assertNotEqual(version, None)

        response = self.client.get_json(make_url_for_lib(lib_key))
        self.assertEqual(response.status_code, 200)
        info = parse_json(response)
        self.assertEqual(info['display_name'], lib.display_name)
        self.assertEqual(info['library_id'], unicode(lib_key))
        self.assertEqual(info['previous_version'], None)
        self.assertNotEqual(info['version'], None)
        self.assertNotEqual(info['version'], '')
        self.assertEqual(info['version'], unicode(version))

    def test_get_lib_edit_html(self):
        """
        Test that we can get the studio view for editing a library using /library/:key/
        """
        lib = LibraryFactory.create()

        response = self.client.get(make_url_for_lib(lib.location.library_key))
        self.assertEqual(response.status_code, 200)
        self.assertIn("<html", response.content)
        self.assertIn(lib.display_name, response.content)

    @ddt.data('library-v1:Nonexistent+library', 'course-v1:Org+Course', 'course-v1:Org+Course+Run', 'invalid')
    def test_invalid_keys(self, key_str):
        """
        Check that various Nonexistent/invalid keys give 404 errors
        """
        response = self.client.get_json(make_url_for_lib(key_str))
        self.assertEqual(response.status_code, 404)

    def test_bad_http_verb_with_lib_key(self):
        """
        We should get an error if we do weird requests to /library/
        """
        lib = LibraryFactory.create()
        for verb in ("post", "delete", "put"):
            response = getattr(self.client, verb)(make_url_for_lib(lib.location.library_key))
            self.assertEqual(response.status_code, 405)

    def test_no_access(self):
        user, password = self.create_non_staff_user()
        self.client.login(username=user, password=password)

        lib = LibraryFactory.create()
        response = self.client.get(make_url_for_lib(lib.location.library_key))
        self.assertEqual(response.status_code, 403)

    def test_get_component_templates(self):
        """
        Verify that templates for adding discussion and advanced components to
        content libraries are not provided.
        """
        lib = LibraryFactory.create()
        lib.advanced_modules = ['lti']
        lib.save()
        templates = [template['type'] for template in get_component_templates(lib, library=True)]
        self.assertIn('problem', templates)
        self.assertNotIn('discussion', templates)
        self.assertNotIn('advanced', templates)

    def test_manage_library_users(self):
        """
        Simple test that the Library "User Access" view works.
        Also tests that we can use the REST API to assign a user to a library.
        """
        library = LibraryFactory.create()
        extra_user, _ = self.create_non_staff_user()
        manage_users_url = reverse_library_url('manage_library_users', unicode(library.location.library_key))

        response = self.client.get(manage_users_url)
        self.assertEqual(response.status_code, 200)
        # extra_user has not been assigned to the library so should not show up in the list:
        self.assertNotIn(extra_user.username, response.content)

        # Now add extra_user to the library:
        user_details_url = reverse_course_url(
            'course_team_handler',
            library.location.library_key, kwargs={'email': extra_user.email}
        )
        edit_response = self.client.ajax_post(user_details_url, {"role": LibraryUserRole.ROLE})
        self.assertIn(edit_response.status_code, (200, 204))

        # Now extra_user should apear in the list:
        response = self.client.get(manage_users_url)
        self.assertEqual(response.status_code, 200)
        self.assertIn(extra_user.username, response.content)
class TestCourseListing(ModuleStoreTestCase):
    """
    Unit tests for getting the list of courses for a logged in user
    """
    def setUp(self):
        """
        Add a user and a course
        """
        super(TestCourseListing, self).setUp()
        # create and log in a staff user.
        # create and log in a non-staff user
        self.user = UserFactory()
        self.factory = RequestFactory()
        self.client = AjaxEnabledTestClient()
        self.client.login(username=self.user.username, password='******')
        self.course_create_rerun_url = reverse('course_handler')
        source_course = CourseFactory.create(
            org='origin',
            number='the_beginning',
            run='first',
            display_name='the one and only',
            start=datetime.utcnow()
        )
        self.source_course_key = source_course.id

        for role in [CourseInstructorRole, CourseStaffRole]:
            role(self.source_course_key).add_users(self.user)

    def tearDown(self):
        """
        Reverse the setup
        """
        self.client.logout()
        ModuleStoreTestCase.tearDown(self)

    def test_rerun(self):
        """
        Just testing the functionality the view handler adds over the tasks tested in test_clone_course
        """
        response = self.client.ajax_post(self.course_create_rerun_url, {
            'source_course_key': unicode(self.source_course_key),
            'org': self.source_course_key.org, 'course': self.source_course_key.course, 'run': 'copy',
            'display_name': 'not the same old name',
        })
        self.assertEqual(response.status_code, 200)
        data = parse_json(response)
        dest_course_key = CourseKey.from_string(data['destination_course_key'])

        self.assertEqual(dest_course_key.run, 'copy')
        dest_course = self.store.get_course(dest_course_key)
        self.assertEqual(dest_course.start, CourseFields.start.default)

    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_newly_created_course_has_web_certs_enabled(self, store):
        """
        Tests newly created course has web certs enabled by default.
        """
        with modulestore().default_store(store):
            response = self.client.ajax_post(self.course_create_rerun_url, {
                'org': 'orgX',
                'number': 'CS101',
                'display_name': 'Course with web certs enabled',
                'run': '2015_T2'
            })
            self.assertEqual(response.status_code, 200)
            data = parse_json(response)
            new_course_key = CourseKey.from_string(data['course_key'])
            course = self.store.get_course(new_course_key)
            self.assertTrue(course.cert_html_view_enabled)

    @patch.dict('django.conf.settings.FEATURES', {'ORGANIZATIONS_APP': False})
    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_course_creation_without_org_app_enabled(self, store):
        """
        Tests course creation workflow should not create course to org
        link if organizations_app is not enabled.
        """
        with modulestore().default_store(store):
            response = self.client.ajax_post(self.course_create_rerun_url, {
                'org': 'orgX',
                'number': 'CS101',
                'display_name': 'Course with web certs enabled',
                'run': '2015_T2'
            })
            self.assertEqual(response.status_code, 200)
            data = parse_json(response)
            new_course_key = CourseKey.from_string(data['course_key'])
            course_orgs = get_course_organizations(new_course_key)
            self.assertEqual(course_orgs, [])

    @patch.dict('django.conf.settings.FEATURES', {'ORGANIZATIONS_APP': True})
    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_course_creation_with_org_not_in_system(self, store):
        """
        Tests course creation workflow when course organization does not exist
        in system.
        """
        with modulestore().default_store(store):
            response = self.client.ajax_post(self.course_create_rerun_url, {
                'org': 'orgX',
                'number': 'CS101',
                'display_name': 'Course with web certs enabled',
                'run': '2015_T2'
            })
            self.assertEqual(response.status_code, 400)
            data = parse_json(response)
            self.assertIn(u'Organization you selected does not exist in the system', data['error'])

    @patch.dict('django.conf.settings.FEATURES', {'ORGANIZATIONS_APP': True})
    @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
    def test_course_creation_with_org_in_system(self, store):
        """
        Tests course creation workflow when course organization exist in system.
        """
        add_organization({
            'name': 'Test Organization',
            'short_name': 'orgX',
            'description': 'Testing Organization Description',
        })
        with modulestore().default_store(store):
            response = self.client.ajax_post(self.course_create_rerun_url, {
                'org': 'orgX',
                'number': 'CS101',
                'display_name': 'Course with web certs enabled',
                'run': '2015_T2'
            })
            self.assertEqual(response.status_code, 200)
            data = parse_json(response)
            new_course_key = CourseKey.from_string(data['course_key'])
            course_orgs = get_course_organizations(new_course_key)
            self.assertEqual(len(course_orgs), 1)
            self.assertEqual(course_orgs[0]['short_name'], 'orgX')
Example #57
0
class AuthTestCase(ContentStoreTestCase):
    """Check that various permissions-related things work"""
    def setUp(self):
        self.email = '*****@*****.**'
        self.pw = 'xyz'
        self.username = '******'
        self.client = AjaxEnabledTestClient()
        # clear the cache so ratelimiting won't affect these tests
        cache.clear()

    def check_page_get(self, url, expected):
        resp = self.client.get_html(url)
        self.assertEqual(resp.status_code, expected)
        return resp

    def test_public_pages_load(self):
        """Make sure pages that don't require login load without error."""
        pages = (
            reverse('login'),
            reverse('signup'),
        )
        for page in pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, 200)

    def test_create_account_errors(self):
        # No post data -- should fail
        resp = self.client.post('/create_account', {})
        self.assertEqual(resp.status_code, 400)
        data = parse_json(resp)
        self.assertEqual(data['success'], False)

    def test_create_account(self):
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)

    def test_create_account_username_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account(self.username, "*****@*****.**", "password")
        # we have a constraint on unique usernames, so this should fail
        self.assertEqual(resp.status_code, 400)

    def test_create_account_pw_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account("abcdef", "*****@*****.**", self.pw)
        # we can have two users with the same password, so this should succeed
        self.assertEqual(resp.status_code, 200)

    @unittest.skipUnless(settings.SOUTH_TESTS_MIGRATE,
                         "South migrations required")
    def test_create_account_email_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account("abcdef", self.email, "password")
        # This is tricky. Django's user model doesn't have a constraint on
        # unique email addresses, but we *add* that constraint during the
        # migration process:
        # see common/djangoapps/student/migrations/0004_add_email_index.py
        #
        # The behavior we *want* is for this account creation request
        # to fail, due to this uniqueness constraint, but the request will
        # succeed if the migrations have not run.
        self.assertEqual(resp.status_code, 400)

    def test_login(self):
        self.create_account(self.username, self.email, self.pw)

        # Not activated yet.  Login should fail.
        resp = self._login(self.email, self.pw)
        data = parse_json(resp)
        self.assertFalse(data['success'])

        self.activate_user(self.email)

        # Now login should work
        self.login(self.email, self.pw)

    def test_login_ratelimited(self):
        # try logging in 30 times, the default limit in the number of failed
        # login attempts in one 5 minute period before the rate gets limited
        for i in xrange(30):
            resp = self._login(self.email, 'wrong_password{0}'.format(i))
            self.assertEqual(resp.status_code, 200)
        resp = self._login(self.email, 'wrong_password')
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertFalse(data['success'])
        self.assertIn('Too many failed login attempts.', data['value'])

    @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3)
    @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2)
    def test_excessive_login_failures(self):
        # try logging in 3 times, the account should get locked for 3 seconds
        # note we want to keep the lockout time short, so we don't slow down the tests

        with mock.patch.dict('django.conf.settings.FEATURES',
                             {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}):
            self.create_account(self.username, self.email, self.pw)
            self.activate_user(self.email)

            for i in xrange(3):
                resp = self._login(self.email, 'wrong_password{0}'.format(i))
                self.assertEqual(resp.status_code, 200)
                data = parse_json(resp)
                self.assertFalse(data['success'])
                self.assertIn('Email or password is incorrect.', data['value'])

            # now the account should be locked

            resp = self._login(self.email, 'wrong_password')
            self.assertEqual(resp.status_code, 200)
            data = parse_json(resp)
            self.assertFalse(data['success'])
            self.assertIn(
                'This account has been temporarily locked due to excessive login failures. Try again later.',
                data['value'])

            with freeze_time('2100-01-01'):
                self.login(self.email, self.pw)

            # make sure the failed attempt counter gets reset on successful login
            resp = self._login(self.email, 'wrong_password')
            self.assertEqual(resp.status_code, 200)
            data = parse_json(resp)
            self.assertFalse(data['success'])

            # account should not be locked out after just one attempt
            self.login(self.email, self.pw)

            # do one more login when there is no bad login counter row at all in the database to
            # test the "ObjectNotFound" case
            self.login(self.email, self.pw)

    def test_login_link_on_activation_age(self):
        self.create_account(self.username, self.email, self.pw)
        # we want to test the rendering of the activation page when the user isn't logged in
        self.client.logout()
        resp = self._activate_user(self.email)
        self.assertEqual(resp.status_code, 200)

        # check the the HTML has links to the right login page. Note that this is merely a content
        # check and thus could be fragile should the wording change on this page
        expected = 'You can now <a href="' + reverse('login') + '">login</a>.'
        self.assertIn(expected, resp.content)

    def test_private_pages_auth(self):
        """Make sure pages that do require login work."""
        auth_pages = ('/course/', )

        # These are pages that should just load when the user is logged in
        # (no data needed)
        simple_auth_pages = ('/course/', )

        # need an activated user
        self.test_create_account()

        # Create a new session
        self.client = AjaxEnabledTestClient()

        # Not logged in.  Should redirect to login.
        print('Not logged in')
        for page in auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=302)

        # Logged in should work.
        self.login(self.email, self.pw)

        print('Logged in')
        for page in simple_auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=200)

    def test_index_auth(self):

        # not logged in.  Should return a redirect.
        resp = self.client.get_html('/course/')
        self.assertEqual(resp.status_code, 302)

        # Logged in should work.

    @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1)
    def test_inactive_session_timeout(self):
        """
        Verify that an inactive session times out and redirects to the
        login page
        """
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)

        self.login(self.email, self.pw)

        # make sure we can access courseware immediately
        course_url = '/course/'
        resp = self.client.get_html(course_url)
        self.assertEquals(resp.status_code, 200)

        # then wait a bit and see if we get timed out
        time.sleep(2)

        resp = self.client.get_html(course_url)

        # re-request, and we should get a redirect to login page
        self.assertRedirects(resp,
                             settings.LOGIN_REDIRECT_URL + '?next=/course/')
Example #58
0
class AuthTestCase(ContentStoreTestCase):
    """Check that various permissions-related things work"""

    def setUp(self):
        self.email = '*****@*****.**'
        self.pw = 'xyz'
        self.username = '******'
        self.client = AjaxEnabledTestClient()
        # clear the cache so ratelimiting won't affect these tests
        cache.clear()

    def check_page_get(self, url, expected):
        resp = self.client.get_html(url)
        self.assertEqual(resp.status_code, expected)
        return resp

    def test_public_pages_load(self):
        """Make sure pages that don't require login load without error."""
        pages = (
            reverse('login'),
            reverse('signup'),
        )
        for page in pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, 200)

    def test_create_account_errors(self):
        # No post data -- should fail
        resp = self.client.post('/create_account', {})
        self.assertEqual(resp.status_code, 400)
        data = parse_json(resp)
        self.assertEqual(data['success'], False)

    def test_create_account(self):
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)

    def test_create_account_username_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account(self.username, "*****@*****.**", "password")
        # we have a constraint on unique usernames, so this should fail
        self.assertEqual(resp.status_code, 400)

    def test_create_account_pw_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account("abcdef", "*****@*****.**", self.pw)
        # we can have two users with the same password, so this should succeed
        self.assertEqual(resp.status_code, 200)

    @unittest.skipUnless(settings.SOUTH_TESTS_MIGRATE, "South migrations required")
    def test_create_account_email_already_exists(self):
        User.objects.create_user(self.username, self.email, self.pw)
        resp = self._create_account("abcdef", self.email, "password")
        # This is tricky. Django's user model doesn't have a constraint on
        # unique email addresses, but we *add* that constraint during the
        # migration process:
        # see common/djangoapps/student/migrations/0004_add_email_index.py
        #
        # The behavior we *want* is for this account creation request
        # to fail, due to this uniqueness constraint, but the request will
        # succeed if the migrations have not run.
        self.assertEqual(resp.status_code, 400)

    def test_login(self):
        self.create_account(self.username, self.email, self.pw)

        # Not activated yet.  Login should fail.
        resp = self._login(self.email, self.pw)
        data = parse_json(resp)
        self.assertFalse(data['success'])

        self.activate_user(self.email)

        # Now login should work
        self.login(self.email, self.pw)

    def test_login_ratelimited(self):
        # try logging in 30 times, the default limit in the number of failed
        # login attempts in one 5 minute period before the rate gets limited
        for i in xrange(30):
            resp = self._login(self.email, 'wrong_password{0}'.format(i))
            self.assertEqual(resp.status_code, 200)
        resp = self._login(self.email, 'wrong_password')
        self.assertEqual(resp.status_code, 200)
        data = parse_json(resp)
        self.assertFalse(data['success'])
        self.assertIn('Too many failed login attempts.', data['value'])

    @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED=3)
    @override_settings(MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS=2)
    def test_excessive_login_failures(self):
        # try logging in 3 times, the account should get locked for 3 seconds
        # note we want to keep the lockout time short, so we don't slow down the tests

        with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': True}):
            self.create_account(self.username, self.email, self.pw)
            self.activate_user(self.email)

            for i in xrange(3):
                resp = self._login(self.email, 'wrong_password{0}'.format(i))
                self.assertEqual(resp.status_code, 200)
                data = parse_json(resp)
                self.assertFalse(data['success'])
                self.assertIn(
                    'Email or password is incorrect.',
                    data['value']
                )

            # now the account should be locked

            resp = self._login(self.email, 'wrong_password')
            self.assertEqual(resp.status_code, 200)
            data = parse_json(resp)
            self.assertFalse(data['success'])
            self.assertIn(
                'This account has been temporarily locked due to excessive login failures. Try again later.',
                data['value']
            )

            with freeze_time('2100-01-01'):
                self.login(self.email, self.pw)

            # make sure the failed attempt counter gets reset on successful login
            resp = self._login(self.email, 'wrong_password')
            self.assertEqual(resp.status_code, 200)
            data = parse_json(resp)
            self.assertFalse(data['success'])

            # account should not be locked out after just one attempt
            self.login(self.email, self.pw)

            # do one more login when there is no bad login counter row at all in the database to
            # test the "ObjectNotFound" case
            self.login(self.email, self.pw)

    def test_login_link_on_activation_age(self):
        self.create_account(self.username, self.email, self.pw)
        # we want to test the rendering of the activation page when the user isn't logged in
        self.client.logout()
        resp = self._activate_user(self.email)
        self.assertEqual(resp.status_code, 200)

        # check the the HTML has links to the right login page. Note that this is merely a content
        # check and thus could be fragile should the wording change on this page
        expected = 'You can now <a href="' + reverse('login') + '">login</a>.'
        self.assertIn(expected, resp.content)

    def test_private_pages_auth(self):
        """Make sure pages that do require login work."""
        auth_pages = (
            '/course/',
        )

        # These are pages that should just load when the user is logged in
        # (no data needed)
        simple_auth_pages = (
            '/course/',
        )

        # need an activated user
        self.test_create_account()

        # Create a new session
        self.client = AjaxEnabledTestClient()

        # Not logged in.  Should redirect to login.
        print('Not logged in')
        for page in auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=302)

        # Logged in should work.
        self.login(self.email, self.pw)

        print('Logged in')
        for page in simple_auth_pages:
            print("Checking '{0}'".format(page))
            self.check_page_get(page, expected=200)

    def test_index_auth(self):

        # not logged in.  Should return a redirect.
        resp = self.client.get_html('/course/')
        self.assertEqual(resp.status_code, 302)

        # Logged in should work.

    @override_settings(SESSION_INACTIVITY_TIMEOUT_IN_SECONDS=1)
    def test_inactive_session_timeout(self):
        """
        Verify that an inactive session times out and redirects to the
        login page
        """
        self.create_account(self.username, self.email, self.pw)
        self.activate_user(self.email)

        self.login(self.email, self.pw)

        # make sure we can access courseware immediately
        course_url = '/course/'
        resp = self.client.get_html(course_url)
        self.assertEquals(resp.status_code, 200)

        # then wait a bit and see if we get timed out
        time.sleep(2)

        resp = self.client.get_html(course_url)

        # re-request, and we should get a redirect to login page
        self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=/course/')
Example #59
0
class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
    """
    Base test class for create, save, and delete
    """
    def setUp(self):
        """
        Shared scaffolding for individual test runs
        """
        super(EntranceExamHandlerTests, self).setUp()
        self.course_key = self.course.id
        self.usage_key = self.course.location
        self.course_url = '/course/{}'.format(six.text_type(self.course.id))
        self.exam_url = '/course/{}/entrance_exam/'.format(
            six.text_type(self.course.id))
        self.milestone_relationship_types = milestones_helpers.get_milestone_relationship_types(
        )

    def test_entrance_exam_milestone_addition(self):
        """
        Unit Test: test addition of entrance exam milestone content
        """
        parent_locator = six.text_type(self.course.location)
        created_block = create_xblock(parent_locator=parent_locator,
                                      user=self.user,
                                      category='chapter',
                                      display_name=('Entrance Exam'),
                                      is_entrance_exam=True)
        add_entrance_exam_milestone(self.course.id, created_block)
        content_milestones = milestones_helpers.get_course_content_milestones(
            six.text_type(self.course.id),
            six.text_type(created_block.location),
            self.milestone_relationship_types['FULFILLS'])
        self.assertTrue(len(content_milestones))
        self.assertEqual(
            len(milestones_helpers.get_course_milestones(self.course.id)), 1)

    def test_entrance_exam_milestone_removal(self):
        """
        Unit Test: test removal of entrance exam milestone content
        """
        parent_locator = six.text_type(self.course.location)
        created_block = create_xblock(parent_locator=parent_locator,
                                      user=self.user,
                                      category='chapter',
                                      display_name=('Entrance Exam'),
                                      is_entrance_exam=True)
        add_entrance_exam_milestone(self.course.id, created_block)
        content_milestones = milestones_helpers.get_course_content_milestones(
            six.text_type(self.course.id),
            six.text_type(created_block.location),
            self.milestone_relationship_types['FULFILLS'])
        self.assertEqual(len(content_milestones), 1)
        user = UserFactory()
        request = RequestFactory().request()
        request.user = user
        remove_entrance_exam_milestone_reference(request, self.course.id)
        content_milestones = milestones_helpers.get_course_content_milestones(
            six.text_type(self.course.id),
            six.text_type(created_block.location),
            self.milestone_relationship_types['FULFILLS'])
        self.assertEqual(len(content_milestones), 0)

    def test_contentstore_views_entrance_exam_post(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_post
        """
        resp = self.client.post(self.exam_url, {},
                                http_accept='application/json')
        self.assertEqual(resp.status_code, 201)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 200)

        # Reload the test course now that the exam module has been added
        self.course = modulestore().get_course(self.course.id)
        metadata = CourseMetadata.fetch_all(self.course)
        self.assertTrue(metadata['entrance_exam_enabled'])
        self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct'])
        self.assertIsNotNone(metadata['entrance_exam_id']['value'])
        self.assertTrue(
            len(
                milestones_helpers.get_course_milestones(
                    six.text_type(self.course.id))))
        content_milestones = milestones_helpers.get_course_content_milestones(
            six.text_type(self.course.id),
            metadata['entrance_exam_id']['value'],
            self.milestone_relationship_types['FULFILLS'])
        self.assertTrue(len(content_milestones))

    def test_contentstore_views_entrance_exam_post_new_sequential_confirm_grader(
            self):
        """
        Unit Test: test_contentstore_views_entrance_exam_post
        """
        resp = self.client.post(self.exam_url, {},
                                http_accept='application/json')
        self.assertEqual(resp.status_code, 201)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 200)

        # Reload the test course now that the exam module has been added
        self.course = modulestore().get_course(self.course.id)

        # Add a new child sequential to the exam module
        # Confirm that the grader type is 'Entrance Exam'
        chapter_locator_string = json.loads(
            resp.content.decode('utf-8')).get('locator')
        # chapter_locator = UsageKey.from_string(chapter_locator_string)
        seq_data = {
            'category': "sequential",
            'display_name': "Entrance Exam Subsection",
            'parent_locator': chapter_locator_string,
        }
        resp = self.client.ajax_post(reverse_url('xblock_handler'), seq_data)
        seq_locator_string = json.loads(
            resp.content.decode('utf-8')).get('locator')
        seq_locator = UsageKey.from_string(seq_locator_string)
        section_grader_type = CourseGradingModel.get_section_grader_type(
            seq_locator)
        self.assertEqual(GRADER_TYPES['ENTRANCE_EXAM'],
                         section_grader_type['graderType'])

    def test_contentstore_views_entrance_exam_get(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_get
        """
        resp = self.client.post(self.exam_url, {
            'entrance_exam_minimum_score_pct':
            settings.ENTRANCE_EXAM_MIN_SCORE_PCT
        },
                                http_accept='application/json')
        self.assertEqual(resp.status_code, 201)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 200)

    def test_contentstore_views_entrance_exam_delete(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_delete
        """
        resp = self.client.post(self.exam_url, {},
                                http_accept='application/json')
        self.assertEqual(resp.status_code, 201)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 200)
        resp = self.client.delete(self.exam_url)
        self.assertEqual(resp.status_code, 204)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 404)

        user = User.objects.create(
            username='******',
            email='*****@*****.**',
            is_active=True,
        )
        user.set_password('test')
        user.save()
        milestones = milestones_helpers.get_course_milestones(
            six.text_type(self.course_key))
        self.assertEqual(len(milestones), 1)
        milestone_key = '{}.{}'.format(milestones[0]['namespace'],
                                       milestones[0]['name'])
        paths = milestones_helpers.get_course_milestones_fulfillment_paths(
            six.text_type(self.course_key),
            milestones_helpers.serialize_user(user))

        # What we have now is a course milestone requirement and no valid fulfillment
        # paths for the specified user.  The LMS is going to have to ignore this situation,
        # because we can't confidently prevent it from occuring at some point in the future.
        # milestone_key_1 =
        self.assertEqual(len(paths[milestone_key]), 0)

        # Re-adding an entrance exam to the course should fix the missing link
        # It wipes out any old entrance exam artifacts and inserts a new exam course chapter/module
        resp = self.client.post(self.exam_url, {},
                                http_accept='application/json')
        self.assertEqual(resp.status_code, 201)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 200)

        # Confirm that we have only one Entrance Exam grader after re-adding the exam (validates SOL-475)
        graders = CourseGradingModel.fetch(self.course_key).graders
        count = 0
        for grader in graders:
            if grader['type'] == GRADER_TYPES['ENTRANCE_EXAM']:
                count += 1
        self.assertEqual(count, 1)

    def test_contentstore_views_entrance_exam_delete_bogus_course(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_delete_bogus_course
        """
        resp = self.client.delete('/course/bad/course/key/entrance_exam')
        self.assertEqual(resp.status_code, 400)

    def test_contentstore_views_entrance_exam_get_bogus_course(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_get_bogus_course
        """
        resp = self.client.get('/course/bad/course/key/entrance_exam')
        self.assertEqual(resp.status_code, 400)

    def test_contentstore_views_entrance_exam_get_bogus_exam(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_get_bogus_exam
        """
        resp = self.client.post(self.exam_url,
                                {'entrance_exam_minimum_score_pct': '50'},
                                http_accept='application/json')

        self.assertEqual(resp.status_code, 201)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 200)
        self.course = modulestore().get_course(self.course.id)
        # Should raise an ItemNotFoundError and return a 404
        updated_metadata = {
            'entrance_exam_id':
            'i4x://org.4/course_4/chapter/ed7c4c6a4d68409998e2c8554c4629d1'
        }

        CourseMetadata.update_from_dict(
            updated_metadata,
            self.course,
            self.user,
        )
        self.course = modulestore().get_course(self.course.id)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 404)

        # Should raise an InvalidKeyError and return a 404
        updated_metadata = {'entrance_exam_id': '123afsdfsad90f87'}

        CourseMetadata.update_from_dict(
            updated_metadata,
            self.course,
            self.user,
        )
        self.course = modulestore().get_course(self.course.id)
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 404)

    def test_contentstore_views_entrance_exam_post_bogus_course(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_post_bogus_course
        """
        resp = self.client.post('/course/bad/course/key/entrance_exam', {},
                                http_accept='application/json')
        self.assertEqual(resp.status_code, 400)

    def test_contentstore_views_entrance_exam_post_invalid_http_accept(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_post_invalid_http_accept
        """
        resp = self.client.post('/course/bad/course/key/entrance_exam', {},
                                http_accept='text/html')
        self.assertEqual(resp.status_code, 400)

    def test_contentstore_views_entrance_exam_get_invalid_user(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_get_invalid_user
        """
        user = User.objects.create(
            username='******',
            email='*****@*****.**',
            is_active=True,
        )
        user.set_password('test')
        user.save()
        self.client = AjaxEnabledTestClient()
        self.client.login(username='******', password='******')
        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 403)

    def test_contentstore_views_entrance_exam_unsupported_method(self):
        """
        Unit Test: test_contentstore_views_entrance_exam_unsupported_method
        """
        resp = self.client.put(self.exam_url)
        self.assertEqual(resp.status_code, 405)

    def test_entrance_exam_view_direct_missing_score_setting(self):
        """
        Unit Test: test_entrance_exam_view_direct_missing_score_setting
        """
        user = UserFactory()
        user.is_staff = True
        request = RequestFactory()
        request.user = user

        resp = create_entrance_exam(request, self.course.id, None)
        self.assertEqual(resp.status_code, 201)

    @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': False})
    def test_entrance_exam_feature_flag_gating(self):
        user = UserFactory()
        user.is_staff = True
        request = RequestFactory()
        request.user = user

        resp = self.client.get(self.exam_url)
        self.assertEqual(resp.status_code, 400)

        resp = create_entrance_exam(request, self.course.id, None)
        self.assertEqual(resp.status_code, 400)

        resp = delete_entrance_exam(request, self.course.id)
        self.assertEqual(resp.status_code, 400)

        # No return, so we'll just ensure no exception is thrown
        update_entrance_exam(request, self.course.id, {})