Example #1
0
    def test_html_safety(self):
        """
        Check that we're handling HTML in a safe way
        """
        html = markup_to_html('<p>Foo</em>', 'html')
        self.assertEqual(html, '<p>Foo</p>')

        html = markup_to_html('Foo<script>alert()</script>', 'html')
        self.assertEqual(html, 'Fooalert()')

        # some junky MSWord-like markup
        html = markup_to_html('Foo<p class="home"><Span style="font-size: 500%">bar</Span></P>', 'html', restricted=True)
        self.assertEqual(html, 'Foo<p>bar</p>')

        html = markup_to_html('A&nbsp;&nbsp;<p>&nbsp;</p><table cellpadding="10"><tr><td colspan=4>B</td></tr></table>',
                              'html', restricted=True)
        self.assertEqual(html, 'A&nbsp;&nbsp;<p>&nbsp;</p>B')


        # unsafe if we ask for it
        html = markup_to_html('Foo<script>alert()</script>', 'html', html_already_safe=True)
        self.assertEqual(html, 'Foo<script>alert()</script>')

        # PageVersions should be saved only with safe HTML
        offering = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=offering, person__userid="ggbaker")

        p = Page(offering=offering, label="Test")
        p.save()
        v1 = PageVersion(page=p, title="T1", wikitext='<em>Some</em> <script>HTML</script>', editor=memb)
        v1.set_markup('html')
        v1.save()

        self.assertEqual(v1.wikitext, '<em>Some</em> HTML')
Example #2
0
 def test_api(self):
     crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
     memb = Member.objects.get(offering=crs, person__userid="ggbaker")
     person = Person.objects.get(userid='ggbaker')
     p = Page(offering=crs, label="PageExists")
     p.save()
     v = PageVersion(page=p, title="Page Exists", wikitext="Original Contents", editor=memb, comment="original page")
     v.save()
     
     from dashboard.models import new_feed_token
     token = new_feed_token()
     
     updata = u"""{
         "userid": "ggbaker",
         "token": "%s",
         "pages": [
             {
                 "label": "Index",
                 "title": "The Coursé Page",
                 "can_read": "ALL",
                 "can_write": "INST",
                 "wikitext-base64": "VGhpcyBwYWdlIGlzIHNwZWNpYWwgaW4gKipzb21lKiogd2F5LiBcKHh+XjIrMSA9IFxmcmFjezF9ezJ9XCkuCgpHb29kYnllIHdvcmxkIQ==",
                 "comment": "page creation comment",
                 "use_math": true
             },
             {
                 "label": "PageExists",
                 "new_label": "PageChanged",
                 "title": "Another Page",
                 "can_read": "STUD",
                 "wikitext": "This is some **new** page\\n\\ncontent."
             }
         ]
     }""" % (token)
     
     # make a request with no auth token in place
     c = Client()
     url = reverse('pages.views.api_import', kwargs={'course_slug': crs.slug})
     response = c.post(url, data=updata.encode('utf8'), content_type="application/json")
     self.assertEquals(response.status_code, 403)
     
     # create token and try again
     person.config['pages-token'] = token
     person.save()
     response = c.post(url, data=updata.encode('utf8'), content_type="application/json")
     self.assertEquals(response.status_code, 200)
     
     # make sure the data arrived
     self.assertEquals(Page.objects.filter(offering=crs, label="PageExists").count(), 0)
     p = Page.objects.get(offering=crs, label="PageChanged")
     v = p.current_version()
     self.assertEqual(v.title, "Another Page")
     self.assertEqual(v.get_wikitext(), "This is some **new** page\n\ncontent.")
     
     p = Page.objects.get(offering=crs, label="Index")
     v = p.current_version()
     self.assertEqual(v.title, u"The Cours\u00e9 Page")
     self.assertEqual(v.get_wikitext(), 'This page is special in **some** way. \\(x~^2+1 = \\frac{1}{2}\\).\n\nGoodbye world!')
     self.assert_('math' in v.config)
     self.assertEqual(v.config['math'], True)
Example #3
0
 def _get_creole(self):
     "Get a Creole class for some PageVersion for generic testing"
     crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
     p = Page(offering=crs, label="Foo")
     p.save()
     pv = PageVersion(page=p)
     pv.get_creole()
     return pv.Creole
Example #4
0
 def _get_creole(self):
     "Get a Creole class for some PageVersion for generic testing"
     crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
     p = Page(offering=crs, label="Foo")
     p.save()
     pv = PageVersion(page=p)
     pv.get_creole()
     return pv.Creole
Example #5
0
    def save(self, editor, *args, **kwargs):
        # also create the PageVersion object.
        upfile = self.cleaned_data['file_attachment']
        pv = PageVersion(file_attachment=upfile, file_mediatype=upfile.content_type,
                         file_name=upfile.name, editor=editor)

        self.instance.offering = self.offering
        pg = super(EditFileForm, self).save(*args, **kwargs)
        pv.page=self.instance
        pv.save()
        return pg
Example #6
0
    def test_macros(self):
        """
        Test macro behaviour
        """
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")

        p = Page(offering=crs, label="Index")
        p.save()
        v = PageVersion(page=p, title="Index Page", wikitext="one +two+ three +four+", editor=memb)
        v.save()

        # no macros defined: rendered as-is
        self.assertEqual(p.current_version().html_contents().strip(), u"<p>one +two+ three +four+</p>")

        mp = Page(offering=crs, label=MACRO_LABEL)
        mp.save()
        mv = PageVersion(page=mp, title="Macros", wikitext="two: 22\nfour: 4444", editor=memb)
        mv.save()

        # macros defined: should be substituted
        self.assertEqual(p.current_version().html_contents().strip(), u"<p>one 22 three 4444</p>")

        mp.safely_delete()

        # macros disappear: back to original
        self.assertEqual(p.current_version().html_contents().strip(), u"<p>one +two+ three +four+</p>")
Example #7
0
    def _import_page(self, url):
        try:
            fh = urllib2.urlopen(url, timeout=20)
            if 'content-type' in fh.headers:
                ctype = fh.headers['content-type'].split(';')[0]
                is_html = ctype in ['text/html', 'application/xhtml+xml']
            else:
                is_html = False
            html = fh.read()
            fh.close()
        except:
            raise forms.ValidationError('Could not fetch "%s".' % (url))

        if not is_html:
            raise forms.ValidationError('Not HTML at "%s".' % (url))

        try:
            wiki, title, urls = self.converter.from_html_full(html)
        except self.converter.ParseError:
            raise forms.ValidationError("Could not parse the HTML file %s." %
                                        (url))

        label = self._labelize(url, title)
        if not title:
            title = label

        page = Page(offering=self.offering, label=label)
        pv = PageVersion(page=page,
                         editor=self.editor,
                         title=title,
                         wikitext=wiki,
                         comment="imported content")
        #print (page, pv, pv.title)
        #print [urlparse.urljoin(url, u) for u in urls]
        return page, pv, urls
Example #8
0
    def test_markup_choice(self):
        """
        Check the distinction between Creole and Markdown pages
        """
        offering = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=offering, person__userid="ggbaker")

        p = Page(offering=offering, label="Test")
        p.save()
        v1 = PageVersion(page=p, title="T1", wikitext='A //test//.', editor=memb, comment="original page")
        v1.save()
        self.assertEqual(v1.html_contents(), '<p>A <em>test</em>.</p>')

        v2 = PageVersion(page=p, title="T1", wikitext='A *test*.', editor=memb, comment="original page")
        v2.set_markup('markdown')
        v2.save()
        self.assertEqual(v2.html_contents(), '<p>A <em>test</em>.</p>')
Example #9
0
    def save(self, editor, *args, **kwargs):
        # create the PageVersion object: distribute the self.cleaned_data values appropriately
        wikitext = self.cleaned_data['markup_content']
        comment = self.cleaned_data['comment']
        title = self.cleaned_data['title']
        pv = PageVersion(title=title, wikitext=wikitext, comment=comment, editor=editor)
        pv.set_markup(self.cleaned_data['_markup'])
        pv.set_math(self.cleaned_data['_math'])

        self.instance.offering = self.offering
        pg = super(EditPageForm, self).save(*args, **kwargs)
        pv.page=self.instance
        pv.save()
        return pg
Example #10
0
    def test_updating(self):
        """
        Test the way the full text index updates

        The real-time indexing is disabled in the tests environments: slows things down too much. Disabled these tests
        as a result, since they don't really match the deployed or devel behaviour.
        """
        return

        res = SearchQuerySet().models(CourseOffering).filter(text='Babbling')
        self.assertEqual(len(res), 1)
        self.assertEquals(res[0].object, self.offering)

        # don't expect CourseOfferings to update automatically
        self.offering.title = 'Something Else'
        self.offering.save()
        res = SearchQuerySet().models(CourseOffering).filter(text='Babbling')
        self.assertEqual(len(res), 1)

        # but a manual refresh should find changes
        self._update_index()
        res = SearchQuerySet().models(CourseOffering).filter(text='Babbling')
        self.assertEqual(len(res), 0)
        res = SearchQuerySet().models(CourseOffering).filter(text='Something')
        self.assertEqual(len(res), 1)

        # but we do update Pages in real time
        res = SearchQuerySet().models(Page).filter(text='fernwhizzles')
        self.assertEqual(len(res), 0)

        # create a page
        p = Page(offering=self.offering,
                 label='SomePage',
                 can_read='ALL',
                 can_write='STAF')
        p.save()
        pv = PageVersion(page=p,
                         title='Some Page',
                         wikitext='This is a page about fernwhizzles.',
                         editor=self.instructor)
        pv.save()
        res = SearchQuerySet().models(Page).filter(text='fernwhizzles')
        self.assertEqual(len(res), 1)

        # update a page
        pv = PageVersion(page=p,
                         title='Some Page',
                         wikitext='This is a page about bobdazzles.',
                         editor=self.instructor)
        pv.save()
        res = SearchQuerySet().models(Page).filter(text='fernwhizzles')
        self.assertEqual(len(res), 0)
        res = SearchQuerySet().models(Page).filter(text='bobdazzles')
        self.assertEqual(len(res), 1)
Example #11
0
def convert_content(request, course_slug, page_label=None):
    """
    Convert between wikicreole and HTML (AJAX called in editor when switching editing modes)
    """
    if request.method != 'POST':
        return ForbiddenResponse(request, 'POST only')
    if 'to' not in request.POST:
        return ForbiddenResponse(request, 'must send "to" language')
    if 'data' not in request.POST:
        return ForbiddenResponse(request, 'must sent source "data"')

    offering = get_object_or_404(CourseOffering, slug=course_slug)

    to = request.POST['to']
    data = request.POST['data']
    if to == 'html':
        # convert wikitext to HTML
        # temporarily change the current version to get the result (but don't save)
        if page_label:
            page = get_object_or_404(Page, offering=offering, label=page_label)
            pv = page.current_version()
        else:
            # create temporary Page for conversion during creation
            p = Page(offering=offering)
            pv = PageVersion(page=p)

        pv.wikitext = data
        pv.diff_from = None
        result = {'data': pv.html_contents()}
        return HttpResponse(json.dumps(result),
                            content_type="application/json")
    else:
        # convert HTML to wikitext
        converter = HTMLWiki([])
        try:
            wiki = converter.from_html(data)
        except converter.ParseError:
            wiki = ''
        result = {'data': wiki}
        return HttpResponse(json.dumps(result),
                            content_type="application/json")
Example #12
0
    def test_pages(self):
        """
        Basic page rendering
        """
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")
        person = Person.objects.get(userid='ggbaker')

        p = Page(offering=crs, label="Index")
        p.save()
        v = PageVersion(page=p, title="Index Page", wikitext="Original Contents", editor=memb)
        v.save()
        p = Page(offering=crs, label="OtherPage")
        p.save()
        v = PageVersion(page=p, title="Other Page", wikitext="Original Contents", editor=memb)
        v.save()
        
        c = Client()
        c.login_user('ggbaker')
        
        # test the basic rendering of the core pages
        test_views(self, c, 'pages.views.', ['index_page', 'all_pages', 'new_page', 'new_file', 'import_site'],
                {'course_slug': crs.slug})

        test_views(self, c, 'pages.views.', ['view_page', 'page_history', 'edit_page', 'import_page'],
                {'course_slug': crs.slug, 'page_label': 'OtherPage'})
Example #13
0
def _delete_pagefile(request, course_slug, page_label, kind):
    """
    Delete page/file
    """
    with django.db.transaction.atomic():
        offering = get_object_or_404(CourseOffering, slug=course_slug)
        page = get_object_or_404(Page, offering=offering, label=page_label)
        version = page.current_version()
        member = _check_allowed(request, offering, page.can_write, page.editdate())
        if not member:
            return ForbiddenResponse(request, 'Not allowed to edit this '+kind+'.')
        can_create = member.role in MEMBER_ROLES[offering.page_creators()]
        if not can_create:
            return ForbiddenResponse(request, 'Not allowed to delete pages in for this offering (must have page-creator permission).')

        from django.core.validators import URLValidator
        from django.core.exceptions import ValidationError
        val = URLValidator()

        redirect = request.POST.get('redirect', 'Index')
        url = request.build_absolute_uri(urljoin(page.get_absolute_url(), redirect))

        try:
            val(url)
        except ValidationError:
            messages.error(request, "Bad redirect URL entered. Not deleted.")
            return HttpResponseRedirect(reverse('offering:pages:edit_page', kwargs={'course_slug': course_slug, 'page_label': page.label}))

        redir_version = PageVersion(page=page, title=version.title, redirect=redirect,
                                    editor=member, comment='automatically generated on deletion')
        redir_version.set_redirect_reason('delete')
        redir_version.save()

        messages.success(request, "Page deleted and will redirect to this location.")
        return HttpResponseRedirect(urljoin(page.get_absolute_url(), redirect))
Example #14
0
    def _sample_setup(self):
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")

        p = Page(offering=crs, label="Index")
        p.save()
        v = PageVersion(page=p, title="Index Page", wikitext="Original Contents", editor=memb)
        v.save()
        p = Page(offering=crs, label="OtherPage")
        p.save()
        v = PageVersion(page=p, title="Other Page", wikitext="Original Contents", editor=memb)
        v.save()

        return crs
Example #15
0
 def test_api(self):
     crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
     memb = Member.objects.get(offering=crs, person__userid="ggbaker")
     person = Person.objects.get(userid='ggbaker')
     p = Page(offering=crs, label="PageExists")
     p.save()
     v = PageVersion(page=p, title="Page Exists", wikitext="Original Contents", editor=memb, comment="original page")
     v.save()
     
     from dashboard.models import new_feed_token
     token = new_feed_token()
     
     updata = u"""{
         "userid": "ggbaker",
         "token": "%s",
         "pages": [
             {
                 "label": "Index",
                 "title": "The Coursé Page",
                 "can_read": "ALL",
                 "can_write": "INST",
                 "wikitext-base64": "VGhpcyBwYWdlIGlzIHNwZWNpYWwgaW4gKipzb21lKiogd2F5LiBcKHh+XjIrMSA9IFxmcmFjezF9ezJ9XCkuCgpHb29kYnllIHdvcmxkIQ==",
                 "comment": "page creation comment",
                 "use_math": true
             },
             {
                 "label": "PageExists",
                 "new_label": "PageChanged",
                 "title": "Another Page",
                 "can_read": "STUD",
                 "wikitext": "This is some **new** page\\n\\ncontent."
             }
         ]
     }""" % (token)
     
     # make a request with no auth token in place
     c = Client()
     url = reverse('pages.views.api_import', kwargs={'course_slug': crs.slug})
     response = c.post(url, data=updata.encode('utf8'), content_type="application/json")
     self.assertEquals(response.status_code, 403)
     
     # create token and try again
     person.config['pages-token'] = token
     person.save()
     response = c.post(url, data=updata.encode('utf8'), content_type="application/json")
     self.assertEquals(response.status_code, 200)
     
     # make sure the data arrived
     self.assertEquals(Page.objects.filter(offering=crs, label="PageExists").count(), 0)
     p = Page.objects.get(offering=crs, label="PageChanged")
     v = p.current_version()
     self.assertEqual(v.title, "Another Page")
     self.assertEqual(v.get_wikitext(), "This is some **new** page\n\ncontent.")
     
     p = Page.objects.get(offering=crs, label="Index")
     v = p.current_version()
     self.assertEqual(v.title, u"The Cours\u00e9 Page")
     self.assertEqual(v.get_wikitext(), 'This page is special in **some** way. \\(x~^2+1 = \\frac{1}{2}\\).\n\nGoodbye world!')
     self.assert_('math' in v.config)
     self.assertEqual(v.config['math'], True)
Example #16
0
    def test_redirect(self):
        """
        Redirecting with redirect stub
        """
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")

        p = Page(offering=crs, label="Test")
        p.save()
        v = PageVersion(page=p, title="Test Page", wikitext="one +two+ three +four+", editor=memb)
        v.save()

        c = Client()
        # normal pages still viewable
        url = reverse('offering:pages:view_page', kwargs={'course_slug': crs.slug, 'page_label': 'Test'})
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        v = PageVersion(page=p, redirect='NewLocation', editor=memb)
        v.save()

        response = c.get(url)
        self.assertEqual(response.status_code, 301)
        redir_url = reverse('offering:pages:view_page', kwargs={'course_slug': crs.slug, 'page_label': 'NewLocation'})
        self.assertTrue(response['location'].endswith(redir_url))
Example #17
0
    def test_macros(self):
        """
        Test macro behaviour
        """
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")

        p = Page(offering=crs, label="Index")
        p.save()
        v = PageVersion(page=p, title="Index Page", wikitext="one +two+ three +four+", editor=memb)
        v.save()

        # no macros defined: rendered as-is
        self.assertEqual(p.current_version().html_contents().strip(), "<p>one +two+ three +four+</p>")

        mp = Page(offering=crs, label=MACRO_LABEL)
        mp.save()
        mv = PageVersion(page=mp, title="Macros", wikitext="two: 22\nfour: 4444", editor=memb)
        mv.save()

        # macros defined: should be substituted
        self.assertEqual(p.current_version().html_contents().strip(), "<p>one 22 three 4444</p>")

        mp.label = 'NOT_MACROS'
        mp.save()

        # macros disappear: back to original
        self.assertEqual(p.current_version().html_contents().strip(), "<p>one +two+ three +four+</p>")
Example #18
0
def convert_content(request, course_slug, page_label=None):
    """
    Convert between wikicreole and HTML (AJAX called in editor when switching editing modes)
    """
    if request.method != 'POST':
        return ForbiddenResponse(request, 'POST only')
    if 'to' not in request.POST:
        return ForbiddenResponse(request, 'must send "to" language')
    if 'data' not in request.POST:
        return ForbiddenResponse(request, 'must sent source "data"')

    offering = get_object_or_404(CourseOffering, slug=course_slug)
    
    to = request.POST['to']
    data = request.POST['data']
    if to == 'html':
        # convert wikitext to HTML
        # temporarily change the current version to get the result (but don't save)
        if page_label:
            page = get_object_or_404(Page, offering=offering, label=page_label)
            pv = page.current_version()
        else:
            # create temporary Page for conversion during creation
            p = Page(offering=offering)
            pv = PageVersion(page=p)
        
        pv.wikitext = data
        pv.diff_from = None
        result = {'data': pv.html_contents()}
        return HttpResponse(json.dumps(result), content_type="application/json")
    else:
        # convert HTML to wikitext
        converter = HTMLWiki([])
        try:
            wiki = converter.from_html(data)
        except converter.ParseError:
            wiki = ''
        result = {'data': wiki}
        return HttpResponse(json.dumps(result), content_type="application/json")
Example #19
0
    def _sample_setup(self):
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")

        p = Page(offering=crs, label="Index")
        p.save()
        v = PageVersion(page=p, title="Index Page", wikitext="Original Contents", editor=memb)
        v.save()
        p = Page(offering=crs, label="OtherPage")
        p.save()
        v = PageVersion(page=p, title="Other Page", wikitext="Original Contents", editor=memb)
        v.save()

        return crs
Example #20
0
    def save(self, editor, *args, **kwargs):
        # also create the PageVersion object.
        wikitext = self.cleaned_data['wikitext']
        comment = self.cleaned_data['comment']
        title = self.cleaned_data['title']
        pv = PageVersion(title=title,
                         wikitext=wikitext,
                         comment=comment,
                         editor=editor)
        # set config data
        if 'math' in self.cleaned_data:
            pv.set_math(self.cleaned_data['math'])

        self.instance.offering = self.offering
        pg = super(EditPageForm, self).save(*args, **kwargs)
        pv.page = self.instance
        pv.save()
        return pg
Example #21
0
    def test_pages(self):
        """
        Basic page rendering
        """
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")
        person = Person.objects.get(userid='ggbaker')

        p = Page(offering=crs, label="Index")
        p.save()
        v = PageVersion(page=p,
                        title="Index Page",
                        wikitext="Original Contents",
                        editor=memb)
        v.save()
        p = Page(offering=crs, label="OtherPage")
        p.save()
        v = PageVersion(page=p,
                        title="Other Page",
                        wikitext="Original Contents",
                        editor=memb)
        v.save()

        c = Client()
        c.login_user('ggbaker')

        # test the basic rendering of the core pages
        test_views(
            self, c, 'pages.views.',
            ['index_page', 'all_pages', 'new_page', 'new_file', 'import_site'],
            {'course_slug': crs.slug})

        test_views(self, c, 'pages.views.',
                   ['view_page', 'page_history', 'edit_page', 'import_page'], {
                       'course_slug': crs.slug,
                       'page_label': 'OtherPage'
                   })
Example #22
0
    def test_redirect(self):
        """
        Redirecting with redirect stub
        """
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")

        p = Page(offering=crs, label="Test")
        p.save()
        v = PageVersion(page=p,
                        title="Test Page",
                        wikitext="one +two+ three +four+",
                        editor=memb)
        v.save()

        c = Client()
        # normal pages still viewable
        url = reverse('offering:pages:view_page',
                      kwargs={
                          'course_slug': crs.slug,
                          'page_label': 'Test'
                      })
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        v = PageVersion(page=p, redirect='NewLocation', editor=memb)
        v.save()

        response = c.get(url)
        self.assertEqual(response.status_code, 301)
        redir_url = reverse('offering:pages:view_page',
                            kwargs={
                                'course_slug': crs.slug,
                                'page_label': 'NewLocation'
                            })
        self.assertTrue(response['location'].endswith(redir_url))
Example #23
0
    def test_updating(self):
        """
        Test the way the full text index updates

        The real-time indexing is disabled in the tests environments: slows things down too much. Disabled these tests
        as a result, since they don't really match the deployed or devel behaviour.
        """
        return

        res = SearchQuerySet().models(CourseOffering).filter(text='Babbling')
        self.assertEqual(len(res), 1)
        self.assertEqual(res[0].object, self.offering)

        # don't expect CourseOfferings to update automatically
        self.offering.title = 'Something Else'
        self.offering.save()
        res = SearchQuerySet().models(CourseOffering).filter(text='Babbling')
        self.assertEqual(len(res), 1)

        # but a manual refresh should find changes
        self._update_index()
        res = SearchQuerySet().models(CourseOffering).filter(text='Babbling')
        self.assertEqual(len(res), 0)
        res = SearchQuerySet().models(CourseOffering).filter(text='Something')
        self.assertEqual(len(res), 1)

        # but we do update Pages in real time
        res = SearchQuerySet().models(Page).filter(text='fernwhizzles')
        self.assertEqual(len(res), 0)

        # create a page
        p = Page(offering=self.offering, label='SomePage', can_read='ALL', can_write='STAF')
        p.save()
        pv = PageVersion(page=p, title='Some Page', wikitext='This is a page about fernwhizzles.', editor=self.instructor)
        pv.save()
        res = SearchQuerySet().models(Page).filter(text='fernwhizzles')
        self.assertEqual(len(res), 1)

        # update a page
        pv = PageVersion(page=p, title='Some Page', wikitext='This is a page about bobdazzles.', editor=self.instructor)
        pv.save()
        res = SearchQuerySet().models(Page).filter(text='fernwhizzles')
        self.assertEqual(len(res), 0)
        res = SearchQuerySet().models(Page).filter(text='bobdazzles')
        self.assertEqual(len(res), 1)
Example #24
0
    def test_html_safety(self):
        """
        Check that we're handling HTML in a safe way
        """
        html = markup_to_html('<p>Foo</em>', 'html')
        self.assertEqual(html, '<p>Foo</p>')

        html = markup_to_html('Foo<script>alert()</script>', 'html')
        self.assertEqual(html, 'Fooalert()')

        # some junky MSWord-like markup
        html = markup_to_html(
            'Foo<p class="home"><Span style="font-size: 500%">bar</Span></P>',
            'html',
            restricted=True)
        self.assertEqual(html, 'Foo<p>bar</p>')

        html = markup_to_html(
            'A&nbsp;&nbsp;<p>&nbsp;</p><table cellpadding="10"><tr><td colspan=4>B</td></tr></table>',
            'html',
            restricted=True)
        self.assertEqual(html, 'A&nbsp;&nbsp;<p>&nbsp;</p>B')

        # unsafe if we ask for it
        html = markup_to_html('Foo<script>alert()</script>',
                              'html',
                              html_already_safe=True)
        self.assertEqual(html, 'Foo<script>alert()</script>')

        # PageVersions should be saved only with safe HTML
        offering = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=offering, person__userid="ggbaker")

        p = Page(offering=offering, label="Test")
        p.save()
        v1 = PageVersion(page=p,
                         title="T1",
                         wikitext='<em>Some</em> <script>HTML</script>',
                         editor=memb)
        v1.set_markup('html')
        v1.save()

        self.assertEqual(v1.wikitext, '<em>Some</em> HTML')
Example #25
0
    def test_markup_choice(self):
        """
        Check the distinction between Creole and Markdown pages
        """
        offering = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=offering, person__userid="ggbaker")

        p = Page(offering=offering, label="Test")
        p.save()
        v1 = PageVersion(page=p,
                         title="T1",
                         wikitext='A //test//.',
                         editor=memb,
                         comment="original page")
        v1.save()
        self.assertEqual(v1.html_contents(), '<p>A <em>test</em>.</p>')

        v2 = PageVersion(page=p,
                         title="T1",
                         wikitext='A *test*.',
                         editor=memb,
                         comment="original page")
        v2.set_markup('markdown')
        v2.save()
        self.assertEqual(v2.html_contents(), '<p>A <em>test</em>.</p>')
Example #26
0
    def test_permissions(self):
        """
        Test page access control behaviour.
        """
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.filter(offering=crs, role='INST').first()
        inst = memb.person
        ta = Member.objects.filter(offering=crs, role='TA').first().person
        stud = Member.objects.filter(offering=crs, role='STUD').first().person
        non_member = Person.objects.get(userid='dixon')
        assert not Member.objects.filter(offering=crs, person=non_member)

        p = Page(offering=crs, label="Test", can_read='STAF', can_write='INST')
        p.save()
        v = PageVersion(page=p, title="Test Page", wikitext="Page contents", editor=memb)
        v.save()

        # page-viewing permissions
        c = Client()
        url = reverse('offering:pages:view_page', kwargs={'course_slug': crs.slug, 'page_label': 'Test'})

        c.logout()
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        c.login_user(inst.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        c.login_user(ta.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        c.login_user(stud.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        c.login_user(non_member.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        # ... but with a PagePermission object, non_member can access
        pp = PagePermission(person=non_member, offering=crs, role='INST')
        pp.save()
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        # page-editing permissions
        url = reverse('offering:pages:edit_page', kwargs={'course_slug': crs.slug, 'page_label': 'Test'})

        c.logout()
        response = c.get(url)
        self.assertEqual(response.status_code, 302) # redirect to log in

        c.login_user(inst.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        c.login_user(ta.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        c.login_user(stud.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        # editing with PagePermission not implemented
        c.login_user(non_member.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)
Example #27
0
    def test_version_diffs(self):
        "Test the old version diffing."
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")

        p = Page(offering=crs, label="Test")
        p.save()
        v1 = PageVersion(page=p,
                         title="T1",
                         wikitext=contents1,
                         editor=memb,
                         comment="original page")
        v1.save()
        v2 = PageVersion(page=p,
                         title="T2",
                         wikitext=contents2,
                         editor=memb,
                         comment="some changes")
        v2.save()
        v3 = PageVersion(page=p,
                         title="T3",
                         wikitext=contents3,
                         editor=memb,
                         comment="total rewrite")
        v3.save()

        # refresh changes in DB
        v1 = PageVersion.objects.get(id=v1.id)
        v2 = PageVersion.objects.get(id=v2.id)
        v3 = PageVersion.objects.get(id=v3.id)

        # make sure the contents survived
        self.assertEqual(v1.get_wikitext(), contents1)
        self.assertEqual(v2.get_wikitext(), contents2)
        self.assertEqual(v3.get_wikitext(), contents3)
        self.assertEqual(v1.title, "T1")
        self.assertEqual(v2.title, "T2")
        self.assertEqual(v3.title, "T3")

        # make sure the diff got stored for incremental changes
        self.assertEqual(v1.wikitext, '')
        self.assertEqual(v1.diff_from_id, v2.id)

        # ... but big changes are stored verbatim
        self.assertEqual(v2.wikitext, contents2)
        self.assertEqual(v2.diff_from, None)

        # ... and the head has the current contents
        self.assertEqual(v3.wikitext, contents3)
        self.assertEqual(v3.diff_from, None)
Example #28
0
def _pages_from_json(request, offering, data):
    with django.db.transaction.atomic():
        try:
            data = data.decode('utf-8-sig')
        except UnicodeDecodeError:
            raise ValidationError(u"Bad UTF-8 data in file.")

        try:
            data = json.loads(data)
        except ValueError as e:
            raise ValidationError(u'JSON decoding error.  Exception was: "' +
                                  str(e) + '"')

        if not isinstance(data, dict):
            raise ValidationError(
                u'Outer JSON data structure must be an object.')
        if 'userid' not in data or 'token' not in data:
            raise ValidationError(
                u'Outer JSON data object must contain keys "userid" and "token".'
            )
        if 'pages' not in data:
            raise ValidationError(
                u'Outer JSON data object must contain keys "pages".')
        if not isinstance(data['pages'], list):
            raise ValidationError(u'Value for "pages" must be a list.')

        try:
            user = Person.objects.get(userid=data['userid'])
            member = Member.objects.exclude(role='DROP').get(person=user,
                                                             offering=offering)
        except Person.DoesNotExist, Member.DoesNotExist:
            raise ValidationError(u'Person with that userid does not exist.')

        if 'pages-token' not in user.config or user.config[
                'pages-token'] != data['token']:
            e = ValidationError(u'Could not validate authentication token.')
            e.status = 403
            raise e

        # if we get this far, the user is authenticated and we can start processing the pages...

        for i, pdata in enumerate(data['pages']):
            if not isinstance(pdata, dict):
                raise ValidationError(
                    u'Page #%i entry structure must be an object.' % (i))
            if 'label' not in pdata:
                raise ValidationError(
                    u'Page #%i entry does not have a "label".' % (i))

            # handle changes to the Page object
            pages = Page.objects.filter(offering=offering,
                                        label=pdata['label'])
            if pages:
                page = pages[0]
                old_ver = page.current_version()
            else:
                page = Page(offering=offering, label=pdata['label'])
                old_ver = None

            # check write permissions

            # mock the request object enough to satisfy _check_allowed()
            class FakeRequest(object):
                def is_authenticated(self):
                    return True

            fake_request = FakeRequest()
            fake_request.user = FakeRequest()
            fake_request.user.username = user.userid

            if old_ver:
                m = _check_allowed(fake_request, offering, page.can_write,
                                   page.editdate())
            else:
                m = _check_allowed(fake_request, offering,
                                   offering.page_creators())
            if not m:
                raise ValidationError(u'You can\'t edit page #%i.' % (i))

            # handle Page attributes
            if 'can_read' in pdata:
                if type(pdata['can_read']
                        ) != unicode or pdata['can_read'] not in ACL_DESC:
                    raise ValidationError(
                        u'Page #%i "can_read" value must be one of %s.' %
                        (i, ','.join(ACL_DESC.keys())))

                page.can_read = pdata['can_read']

            if 'can_write' in pdata:
                if type(pdata['can_write']) != unicode or pdata[
                        'can_write'] not in WRITE_ACL_DESC:
                    raise ValidationError(
                        u'Page #%i "can_write" value must be one of %s.' %
                        (i, ','.join(WRITE_ACL_DESC.keys())))
                if m.role == 'STUD':
                    raise ValidationError(
                        u'Page #%i: students can\'t change can_write value.' %
                        (i))
                page.can_write = pdata['can_write']

            if 'new_label' in pdata:
                if type(pdata['new_label']) != unicode:
                    raise ValidationError(
                        u'Page #%i "new_label" value must be a string.' % (i))
                if m.role == 'STUD':
                    raise ValidationError(
                        u'Page #%i: students can\'t change label value.' % (i))
                if Page.objects.filter(offering=offering,
                                       label=pdata['new_label']):
                    raise ValidationError(
                        u'Page #%i: there is already a page with that "new_label".'
                        % (i))

                page.label = pdata['new_label']

            page.save()

            # handle PageVersion changes
            ver = PageVersion(page=page, editor=member)

            if 'title' in pdata:
                if type(pdata['title']) != unicode:
                    raise ValidationError(
                        u'Page #%i "title" value must be a string.' % (i))

                ver.title = pdata['title']
            elif old_ver:
                ver.title = old_ver.title
            else:
                raise ValidationError(
                    u'Page #%i has no "title" for new page.' % (i))

            if 'comment' in pdata:
                if type(pdata['comment']) != unicode:
                    raise ValidationError(
                        u'Page #%i "comment" value must be a string.' % (i))

                ver.comment = pdata['comment']

            if 'use_math' in pdata:
                if type(pdata['use_math']) != bool:
                    raise ValidationError(
                        u'Page #%i "comment" value must be a boolean.' % (i))

                ver.set_math(pdata['use_math'])

            if 'wikitext-base64' in pdata:
                if type(pdata['wikitext-base64']) != unicode:
                    raise ValidationError(
                        u'Page #%i "wikitext-base64" value must be a string.' %
                        (i))
                try:
                    wikitext = base64.b64decode(pdata['wikitext-base64'])
                except TypeError:
                    raise ValidationError(
                        u'Page #%i "wikitext-base64" contains bad base BASE64 data.'
                        % (i))

                ver.wikitext = wikitext
            elif 'wikitext' in pdata:
                if type(pdata['wikitext']) != unicode:
                    raise ValidationError(
                        u'Page #%i "wikitext" value must be a string.' % (i))

                ver.wikitext = pdata['wikitext']
            elif old_ver:
                ver.wikitext = old_ver.wikitext
            else:
                raise ValidationError(
                    u'Page #%i has no wikitext for new page.' % (i))

            ver.save()

        return user
Example #29
0
def _edit_pagefile(request, course_slug, page_label, kind):
    """
    View to create and edit pages
    """
    if request.method == 'POST' and 'delete' in request.POST and request.POST[
            'delete'] == 'yes':
        return _delete_pagefile(request, course_slug, page_label, kind)
    with django.db.transaction.atomic():
        offering = get_object_or_404(CourseOffering, slug=course_slug)
        if page_label:
            page = get_object_or_404(Page, offering=offering, label=page_label)
            version = page.current_version()
            member = _check_allowed(request, offering, page.can_write,
                                    page.editdate())
            old_label = page.label
        else:
            page = None
            version = None
            member = _check_allowed(
                request, offering,
                offering.page_creators())  # users who can create pages
            old_label = None

        if isinstance(member, PagePermission):
            return ForbiddenResponse(
                request,
                'Editing of pages by additional-permission holders is not implemented. Sorry'
            )

        # make sure we're looking at the right "kind" (page/file)
        if not kind:
            kind = "file" if version.is_filepage() else "page"

        # get the form class we need
        if kind == "page":
            Form = EditPageForm
        else:
            Form = EditFileForm

        # check that we have an allowed member of the course (and can continue)
        if not member:
            return ForbiddenResponse(
                request, 'Not allowed to edit/create this ' + kind + '.')
        restricted = False
        if member.role == 'STUD':
            # students get the restricted version of the form
            Form = Form.restricted_form
            restricted = True

        if request.method == 'POST':
            form = Form(instance=page,
                        offering=offering,
                        data=request.POST,
                        files=request.FILES)
            if form.is_valid():
                instance = form.save(editor=member)

                # clean up weirdness from restricted form
                if 'label' not in form.cleaned_data:
                    # happens when student edits an existing page
                    instance.label = page.label
                if 'can_write' not in form.cleaned_data:
                    # happens only when students create a page
                    instance.can_write = 'STUD'

                if not restricted and 'releasedate' in form.cleaned_data:
                    instance.set_releasedate(form.cleaned_data['releasedate'])
                elif not restricted:
                    instance.set_releasedate(None)

                if not restricted and 'editdate' in form.cleaned_data:
                    instance.set_editdate(form.cleaned_data['editdate'])
                elif not restricted:
                    instance.set_editdate(None)

                instance.redirect = None

                if old_label and old_label != instance.label:
                    # page has been moved to a new URL: leave a redirect in its place
                    redir_page = Page(offering=instance.offering,
                                      label=old_label,
                                      can_read=instance.can_read,
                                      can_write=offering.page_creators())
                    redir_page.set_releasedate(instance.releasedate())
                    redir_page.set_editdate(instance.editdate())
                    redir_page.save()
                    redir_version = PageVersion(
                        page=redir_page,
                        title=version.title,
                        redirect=instance.label,
                        editor=member,
                        comment='automatically generated on label change')
                    redir_version.set_redirect_reason('rename')
                    redir_version.save()
                    messages.info(
                        request,
                        'Page label changed: the old location (%s) will redirect to this page.'
                        % (old_label, ))

                instance.save()

                #LOG EVENT#
                l = LogEntry(userid=request.user.username,
                             description="Edited page %s in %s." %
                             (instance.label, offering),
                             related_object=instance)
                l.save()
                if page:
                    messages.success(
                        request,
                        "Edited " + kind + " \"%s\"." % (instance.label))
                else:
                    messages.success(
                        request,
                        "Created " + kind + " \"%s\"." % (instance.label))

                if not page and instance.label == 'Index' and not offering.url(
                ):
                    # new Index page but no existing course URL: set as course web page
                    url = settings.BASE_ABS_URL + instance.get_absolute_url()
                    offering.set_url(url)
                    offering.save()
                    messages.info(request, "Set course URL to new Index page.")

                return HttpResponseRedirect(
                    reverse('offering:pages:view_page',
                            kwargs={
                                'course_slug': course_slug,
                                'page_label': instance.label
                            }))
        else:
            form = Form(instance=page, offering=offering)
            if 'label' in request.GET:
                label = request.GET['label']
                if label == 'Index':
                    form.initial['title'] = offering.name()
                    form.fields[
                        'label'].help_text += u'\u2014the label "Index" indicates the front page for this course.'
                elif label == MACRO_LABEL:
                    form.initial['can_read'] = 'INST'
                    form.initial['can_write'] = 'INST'
                    form.initial['title'] = MACRO_LABEL
                else:
                    form.initial['title'] = label.title()
                form.initial['label'] = label

        context = {
            'offering': offering,
            'page': page,
            'form': form,
            'kind': kind.title(),
            'is_macro_page': form.initial.get('title', None) == MACRO_LABEL,
        }
        return render(request, 'pages/edit_page.html', context)
Example #30
0
def _edit_pagefile(request, course_slug, page_label, kind):
    """
    View to create and edit pages
    """
    if request.method == 'POST' and 'delete' in request.POST and request.POST['delete'] == 'yes':
        return _delete_pagefile(request, course_slug, page_label, kind)
    with django.db.transaction.atomic():
        offering = get_object_or_404(CourseOffering, slug=course_slug)
        if page_label:
            page = get_object_or_404(Page, offering=offering, label=page_label)
            version = page.current_version()
            member = _check_allowed(request, offering, page.can_write, page.editdate())
            old_label = page.label
        else:
            page = None
            version = None
            member = _check_allowed(request, offering, offering.page_creators()) # users who can create pages
            old_label = None

        if isinstance(member, PagePermission):
            return ForbiddenResponse(request, 'Editing of pages by additional-permission holders is not implemented. Sorry')

        # make sure we're looking at the right "kind" (page/file)
        if not kind:
            kind = "file" if version.is_filepage() else "page"

        # get the form class we need
        if kind == "page":
            Form = EditPageForm
        else:
            Form = EditFileForm
        
        # check that we have an allowed member of the course (and can continue)
        if not member:
            return ForbiddenResponse(request, 'Not allowed to edit/create this '+kind+'.')
        restricted = False
        if member.role == 'STUD':
            # students get the restricted version of the form
            Form = Form.restricted_form
            restricted = True
        
        if request.method == 'POST':
            form = Form(instance=page, offering=offering, data=request.POST, files=request.FILES)
            if form.is_valid():
                instance = form.save(editor=member)
                
                # clean up weirdness from restricted form
                if 'label' not in form.cleaned_data:
                    # happens when student edits an existing page
                    instance.label = page.label
                if 'can_write' not in form.cleaned_data:
                    # happens only when students create a page
                    instance.can_write = 'STUD'
                
                if not restricted and 'releasedate' in form.cleaned_data:
                    instance.set_releasedate(form.cleaned_data['releasedate'])
                elif not restricted:
                    instance.set_releasedate(None)

                if not restricted and 'editdate' in form.cleaned_data:
                    instance.set_editdate(form.cleaned_data['editdate'])
                elif not restricted:
                    instance.set_editdate(None)

                instance.redirect = None

                if old_label and old_label != instance.label:
                    # page has been moved to a new URL: leave a redirect in its place
                    redir_page = Page(offering=instance.offering, label=old_label,
                                      can_read=instance.can_read, can_write=offering.page_creators())
                    redir_page.set_releasedate(instance.releasedate())
                    redir_page.set_editdate(instance.editdate())
                    redir_page.save()
                    redir_version = PageVersion(page=redir_page, title=version.title, redirect=instance.label,
                                                editor=member, comment='automatically generated on label change')
                    redir_version.set_redirect_reason('rename')
                    redir_version.save()
                    messages.info(request, 'Page label changed: the old location (%s) will redirect to this page.' % (old_label,))

                instance.save()
                
                #LOG EVENT#
                l = LogEntry(userid=request.user.username,
                      description="Edited page %s in %s." % (instance.label, offering),
                      related_object=instance)
                l.save()
                if page:
                    messages.success(request, "Edited "+kind+" \"%s\"." % (instance.label))
                else:
                    messages.success(request, "Created "+kind+" \"%s\"." % (instance.label))

                if not page and instance.label == 'Index' and not offering.url():
                    # new Index page but no existing course URL: set as course web page
                    url = settings.BASE_ABS_URL + instance.get_absolute_url()
                    offering.set_url(url)
                    offering.save()
                    messages.info(request, "Set course URL to new Index page.")
                
                return HttpResponseRedirect(reverse('offering:pages:view_page', kwargs={'course_slug': course_slug, 'page_label': instance.label}))
        else:
            form = Form(instance=page, offering=offering)
            if 'label' in request.GET:
                label = request.GET['label']
                if label == 'Index':
                    form.initial['title'] = offering.name()
                    form.fields['label'].help_text += '\u2014the label "Index" indicates the front page for this course.'
                elif label == MACRO_LABEL:
                    form.initial['can_read'] = 'INST'
                    form.initial['can_write'] = 'INST'
                    form.initial['title'] = MACRO_LABEL
                else:
                    form.initial['title'] = label.title()
                form.initial['label'] = label

        context = {
            'offering': offering,
            'page': page,
            'form': form,
            'kind': kind.title(),
            'is_macro_page': form.initial.get('title', None) == MACRO_LABEL,
        }
        return render(request, 'pages/edit_page.html', context)
Example #31
0
    def test_permissions(self):
        """
        Test page access control behaviour.
        """
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.filter(offering=crs, role='INST').first()
        inst = memb.person
        ta = Member.objects.filter(offering=crs, role='TA').first().person
        stud = Member.objects.filter(offering=crs, role='STUD').first().person
        non_member = Person.objects.get(userid='dixon')
        assert not Member.objects.filter(offering=crs, person=non_member)

        p = Page(offering=crs, label="Test", can_read='STAF', can_write='INST')
        p.save()
        v = PageVersion(page=p,
                        title="Test Page",
                        wikitext="Page contents",
                        editor=memb)
        v.save()

        # page-viewing permissions
        c = Client()
        url = reverse('offering:pages:view_page',
                      kwargs={
                          'course_slug': crs.slug,
                          'page_label': 'Test'
                      })

        c.logout()
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        c.login_user(inst.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        c.login_user(ta.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        c.login_user(stud.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        c.login_user(non_member.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        # ... but with a PagePermission object, non_member can access
        pp = PagePermission(person=non_member, offering=crs, role='INST')
        pp.save()
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        # page-editing permissions
        url = reverse('offering:pages:edit_page',
                      kwargs={
                          'course_slug': crs.slug,
                          'page_label': 'Test'
                      })

        c.logout()
        response = c.get(url)
        self.assertEqual(response.status_code, 302)  # redirect to log in

        c.login_user(inst.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 200)

        c.login_user(ta.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        c.login_user(stud.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)

        # editing with PagePermission not implemented
        c.login_user(non_member.userid)
        response = c.get(url)
        self.assertEqual(response.status_code, 403)
Example #32
0
def _pages_from_json(request, offering, data):
    with django.db.transaction.atomic():
        try:
            data = data.decode('utf-8-sig')
        except UnicodeDecodeError:
            raise ValidationError("Bad UTF-8 data in file.")
            
        try:
            data = json.loads(data)
        except ValueError as e:
            raise ValidationError('JSON decoding error.  Exception was: "' + str(e) + '"')
        
        if not isinstance(data, dict):
            raise ValidationError('Outer JSON data structure must be an object.')
        if 'userid' not in data or 'token' not in data:
            raise ValidationError('Outer JSON data object must contain keys "userid" and "token".')
        if 'pages' not in data:
            raise ValidationError('Outer JSON data object must contain keys "pages".')
        if not isinstance(data['pages'], list):
            raise ValidationError('Value for "pages" must be a list.')
        
        try:
            user = Person.objects.get(userid=data['userid'])
            member = Member.objects.exclude(role='DROP').get(person=user, offering=offering)
        except (Person.DoesNotExist, Member.DoesNotExist):
            raise ValidationError('Person with that userid does not exist.')
        
        if 'pages-token' not in user.config or user.config['pages-token'] != data['token']:
            e = ValidationError('Could not validate authentication token.')
            e.status = 403
            raise e
        
        # if we get this far, the user is authenticated and we can start processing the pages...
        
        for i, pdata in enumerate(data['pages']):
            if not isinstance(pdata, dict):
                raise ValidationError('Page #%i entry structure must be an object.' % (i))
            if 'label' not in pdata:
                raise ValidationError('Page #%i entry does not have a "label".' % (i))
            
            # handle changes to the Page object
            pages = Page.objects.filter(offering=offering, label=pdata['label'])
            if pages:
                page = pages[0]
                old_ver = page.current_version()
            else:
                page = Page(offering=offering, label=pdata['label'])
                old_ver = None

            # check write permissions
            
            # mock the request object enough to satisfy _check_allowed()
            class FakeRequest(object):
                is_authenticated = True
            fake_request = FakeRequest()
            fake_request.user = FakeRequest()
            fake_request.user.username = user.userid

            if old_ver:
                m = _check_allowed(fake_request, offering, page.can_write, page.editdate())
            else:
                m = _check_allowed(fake_request, offering, offering.page_creators())
            if not m:
                raise ValidationError('You can\'t edit page #%i.' % (i))
            
            # handle Page attributes
            if 'can_read' in pdata:
                if type(pdata['can_read']) != str or pdata['can_read'] not in ACL_DESC:
                    raise ValidationError('Page #%i "can_read" value must be one of %s.'
                                          % (i, ','.join(list(ACL_DESC.keys()))))
                
                page.can_read = pdata['can_read']

            if 'can_write' in pdata:
                if type(pdata['can_write']) != str or pdata['can_write'] not in WRITE_ACL_DESC:
                    raise ValidationError('Page #%i "can_write" value must be one of %s.'
                                          % (i, ','.join(list(WRITE_ACL_DESC.keys()))))
                if m.role == 'STUD':
                    raise ValidationError('Page #%i: students can\'t change can_write value.' % (i))
                page.can_write = pdata['can_write']
            
            if 'new_label' in pdata:
                if type(pdata['new_label']) != str:
                    raise ValidationError('Page #%i "new_label" value must be a string.' % (i))
                if m.role == 'STUD':
                    raise ValidationError('Page #%i: students can\'t change label value.' % (i))
                if Page.objects.filter(offering=offering, label=pdata['new_label']):
                    raise ValidationError('Page #%i: there is already a page with that "new_label".' % (i))

                page.label = pdata['new_label']

            page.save()

            # handle PageVersion changes
            ver = PageVersion(page=page, editor=member)
            
            if 'title' in pdata:
                if type(pdata['title']) != str:
                    raise ValidationError('Page #%i "title" value must be a string.' % (i))
                
                ver.title = pdata['title']
            elif old_ver:
                ver.title = old_ver.title
            else:
                raise ValidationError('Page #%i has no "title" for new page.' % (i))

            if 'comment' in pdata:
                if type(pdata['comment']) != str:
                    raise ValidationError('Page #%i "comment" value must be a string.' % (i))
                
                ver.comment = pdata['comment']

            if 'use_math' in pdata:
                if type(pdata['use_math']) != bool:
                    raise ValidationError('Page #%i "comment" value must be a boolean.' % (i))

                ver.set_math(pdata['use_math'])

            if 'markup' in pdata:
                if isinstance(pdata['markup'], str):
                    raise ValidationError('Page #%i "markup" value must be a string.' % (i))

                ver.set_markup(pdata['markup'])

            if 'wikitext-base64' in pdata:
                if type(pdata['wikitext-base64']) != str:
                    raise ValidationError('Page #%i "wikitext-base64" value must be a string.' % (i))
                try:
                    wikitext = base64.b64decode(pdata['wikitext-base64']).decode('utf8')
                except TypeError:
                    raise ValidationError('Page #%i "wikitext-base64" contains bad base BASE64 data.' % (i))
                
                ver.wikitext = wikitext
            elif 'wikitext' in pdata:
                if type(pdata['wikitext']) != str:
                    raise ValidationError('Page #%i "wikitext" value must be a string.' % (i))
                
                ver.wikitext = pdata['wikitext']
            elif old_ver:
                ver.wikitext = old_ver.wikitext
            else:
                raise ValidationError('Page #%i has no wikitext for new page.' % (i))

            ver.save()
        
        return user
Example #33
0
    def test_version_diffs(self):
        "Test the old version diffing."
        crs = CourseOffering.objects.get(slug=TEST_COURSE_SLUG)
        memb = Member.objects.get(offering=crs, person__userid="ggbaker")
        
        p = Page(offering=crs, label="Test")        
        p.save()
        v1 = PageVersion(page=p, title="T1", wikitext=contents1, editor=memb, comment="original page")
        v1.save()
        v2 = PageVersion(page=p, title="T2", wikitext=contents2, editor=memb, comment="some changes")
        v2.save()
        v3 = PageVersion(page=p, title="T3", wikitext=contents3, editor=memb, comment="total rewrite")
        v3.save()
        
        # refresh changes in DB
        v1 = PageVersion.objects.get(id=v1.id)
        v2 = PageVersion.objects.get(id=v2.id)
        v3 = PageVersion.objects.get(id=v3.id)
        
        # make sure the contents survived
        self.assertEqual(v1.get_wikitext(), contents1)
        self.assertEqual(v2.get_wikitext(), contents2)
        self.assertEqual(v3.get_wikitext(), contents3)
        self.assertEqual(v1.title, "T1")
        self.assertEqual(v2.title, "T2")
        self.assertEqual(v3.title, "T3")

        # make sure the diff got stored for incremental changes
        self.assertEqual(v1.wikitext, '')
        self.assertEqual(v1.diff_from_id, v2.id)
        
        # ... but big changes are stored verbatim
        self.assertEqual(v2.wikitext, contents2)
        self.assertEqual(v2.diff_from, None)

        # ... and the head has the current contents
        self.assertEqual(v3.wikitext, contents3)
        self.assertEqual(v3.diff_from, None)