diff --git a/CHANGES.txt b/CHANGES.txt index 0d32517..2f8953c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,11 @@ Changelog 1.0b7 (unreleased) ------------------ +* Raise an unauthorized error when authenticated users try to post a comment + on a content object that has discussion disabled. Thanks to vincentfrentin + for reporting this. + [timo] + * Czech translation added. [naro] diff --git a/plone/app/discussion/browser/comments.py b/plone/app/discussion/browser/comments.py index 4a68ea3..6aa1248 100644 --- a/plone/app/discussion/browser/comments.py +++ b/plone/app/discussion/browser/comments.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from Acquisition import aq_inner +from AccessControl import Unauthorized from AccessControl import getSecurityManager from datetime import datetime @@ -108,11 +109,13 @@ class CommentForm(extensible.ExtensibleForm, form.Form): author_email = u"" #author_notification = None - # Captcha check for anonymous users (if Captcha is enabled) + # Captcha check for anonymous users (if Captcha is enabled and + # anonymous commenting is allowed) registry = queryUtility(IRegistry) settings = registry.forInterface(IDiscussionSettings) portal_membership = getToolByName(self.context, 'portal_membership') if settings.captcha != 'disabled' and \ + settings.anonymous_comments and \ portal_membership.isAnonymousUser(): if not 'captcha' in data: data['captcha'] = u"" @@ -123,13 +126,13 @@ class CommentForm(extensible.ExtensibleForm, form.Form): None) captcha.validate(data['captcha']) + # Fetch data from request if 'title' in data: title = data['title'] if 'text' in data: text = data['text'] if 'author_name' in data: author_name = data['author_name'] - if 'author_email' in data: author_email = data['author_email'] #if 'author_notification' in data: @@ -137,6 +140,11 @@ class CommentForm(extensible.ExtensibleForm, form.Form): # The add-comment view is called on the conversation object conversation = IConversation(self.__parent__) + + # Check if conversation is enabled on this content object + if not conversation.enabled(): + raise Unauthorized, "Discussion is not enabled for this content\ + object." if data['in_reply_to']: # Fetch the comment we want to reply to @@ -150,13 +158,14 @@ class CommentForm(extensible.ExtensibleForm, form.Form): portal_membership = getToolByName(self.context, 'portal_membership') - if portal_membership.isAnonymousUser(): + if portal_membership.isAnonymousUser() and \ + settings.anonymous_comments: comment.creator = None comment.author_name = author_name comment.author_email = author_email #comment.author_notification = author_notification comment.creation_date = comment.modification_date = datetime.now() - else: + elif not portal_membership.isAnonymousUser(): member = portal_membership.getAuthenticatedMember() comment.creator = member.id comment.author_username = member.getUserName() @@ -164,7 +173,10 @@ class CommentForm(extensible.ExtensibleForm, form.Form): comment.author_email = member.getProperty('email') #comment.author_notification = comment.author_notification comment.creation_date = comment.modification_date = datetime.now() - + else: + raise Unauthorized, "Anonymous user tries to post a comment, but \ + anonymous commenting is disabled." + # Check if the added comment is a reply to an existing comment # or just a regular reply to the content object. if data['in_reply_to']: diff --git a/plone/app/discussion/tests/test_comments_viewlet.py b/plone/app/discussion/tests/test_comments_viewlet.py index 6cae64e..62ebb1c 100644 --- a/plone/app/discussion/tests/test_comments_viewlet.py +++ b/plone/app/discussion/tests/test_comments_viewlet.py @@ -2,6 +2,8 @@ import unittest from datetime import datetime +from AccessControl import Unauthorized + from Acquisition import Implicit from zope.component import createObject, queryUtility @@ -23,11 +25,15 @@ from zope.component import getMultiAdapter from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName + from Products.CMFPlone.tests import dummy + from Products.Five.testbrowser import Browser + from Products.PloneTestCase.ptc import PloneTestCase from Products.PloneTestCase.ptc import FunctionalTestCase - +from Products.PloneTestCase.setup import portal_owner, default_password + from plone.app.discussion.comment import Comment from plone.app.discussion.browser.comments import CommentsViewlet from plone.app.discussion.browser.comments import CommentForm @@ -43,16 +49,19 @@ class TestCommentForm(PloneTestCase): self.loginAsPortalOwner() typetool = self.portal.portal_types typetool.constructContent('Document', self.portal, 'doc1') - self.portal_discussion = getToolByName(self.portal, - 'portal_discussion', - None) - self.membership_tool = getToolByName(self.folder, 'portal_membership') + self.dtool = getToolByName(self.portal, + 'portal_discussion', + None) + self.dtool.overrideDiscussionFor(self.portal.doc1, False) + self.mtool = getToolByName(self.folder, 'portal_membership', None) self.memberdata = self.portal.portal_memberdata self.request = self.app.REQUEST self.context = getattr(self.portal, 'doc1') - self.viewlet = CommentsViewlet(self.context, self.request, None, None) - + def test_add_comment(self): + # Allow discussion + self.dtool.overrideDiscussionFor(self.portal.doc1, True) + self.viewlet = CommentsViewlet(self.context, self.request, None, None) def make_request(form={}): request = TestRequest() @@ -66,6 +75,7 @@ class TestCommentForm(PloneTestCase): factory=CommentForm, name=u"comment-form") + # The form should return errors if the two required fields are empty request = make_request(form={}) commentForm = getMultiAdapter((self.context, request), @@ -76,7 +86,7 @@ class TestCommentForm(PloneTestCase): self.assertEquals(len(errors), 2) self.failIf(commentForm.handleComment(commentForm, "foo")) - + # The form should return an error if the comment text field is empty request = make_request(form={'form.widgets.text': 'foo'}) commentForm = getMultiAdapter((self.context, request), @@ -85,8 +95,10 @@ class TestCommentForm(PloneTestCase): data, errors = commentForm.extractData() self.assertEquals(len(errors), 1) - - + self.failIf(commentForm.handleComment(commentForm, "foo")) + + # The form is submitted successfully, if all required fields are + # filled out request = make_request(form={'form.widgets.title': 'foo', 'form.widgets.text': 'bar'}) @@ -97,6 +109,76 @@ class TestCommentForm(PloneTestCase): self.assertEquals(len(errors), 0) self.failIf(commentForm.handleComment(commentForm, "foo")) + + + def test_can_not_add_comments_if_discussion_is_not_allowed(self): + """Make sure that comments can't be posted if discussion is disabled. + """ + + # Discussion is disabled by default + + def make_request(form={}): + request = TestRequest() + request.form.update(form) + alsoProvides(request, IFormLayer) + alsoProvides(request, IAttributeAnnotatable) + return request + + provideAdapter(adapts=(Interface, IBrowserRequest), + provides=Interface, + factory=CommentForm, + name=u"comment-form") + + request = make_request(form={'form.widgets.title': 'foo', + 'form.widgets.text': 'bar'}) + + commentForm = getMultiAdapter((self.context, request), + name=u"comment-form") + commentForm.update() + data, errors = commentForm.extractData() + + # No form errors, but raise unauthorized because discussion is not + # allowed + self.assertEquals(len(errors), 0) + self.assertRaises(Unauthorized, + commentForm.handleComment, + commentForm, + "foo") + + def test_add_comment_as_anonymous(self): + """Make sure that anonymous users can't post comments if anonymous + comments are disabled. + """ + + # Anonymous comments are disabled by default + + self.logout() + + def make_request(form={}): + request = TestRequest() + request.form.update(form) + alsoProvides(request, IFormLayer) + alsoProvides(request, IAttributeAnnotatable) + return request + + provideAdapter(adapts=(Interface, IBrowserRequest), + provides=Interface, + factory=CommentForm, + name=u"comment-form") + + request = make_request(form={'form.widgets.title': 'foo', + 'form.widgets.text': 'bar'}) + + commentForm = getMultiAdapter((self.context, request), + name=u"comment-form") + commentForm.update() + data, errors = commentForm.extractData() + + self.assertEquals(len(errors), 0) + self.assertRaises(Unauthorized, + commentForm.handleComment, + commentForm, + "foo") class TestCommentsViewletIntegration(FunctionalTestCase): @@ -108,8 +190,6 @@ class TestCommentsViewletIntegration(FunctionalTestCase): portal_url = self.portal.absolute_url() browser.handleErrors = False - from Products.PloneTestCase.setup import portal_owner, default_password - browser.open(portal_url + '/login_form') browser.getControl(name='__ac_name').value = portal_owner browser.getControl(name='__ac_password').value = default_password @@ -151,7 +231,7 @@ class TestCommentsViewlet(PloneTestCase): self.portal_discussion = getToolByName(self.portal, 'portal_discussion', None) - self.membership_tool = getToolByName(self.folder, 'portal_membership') + self.mtool = getToolByName(self.folder, 'portal_membership') self.memberdata = self.portal.portal_memberdata request = self.app.REQUEST context = getattr(self.portal, 'doc1') @@ -265,7 +345,7 @@ class TestCommentsViewlet(PloneTestCase): def test_get_commenter_portrait(self): # Add a user with a member image - self.membership_tool.addMember('jim', 'Jim', ['Member'], []) + self.mtool.addMember('jim', 'Jim', ['Member'], []) self.memberdata._setPortrait(Image(id='jim', file=dummy.File(), title=''), 'jim') @@ -298,7 +378,7 @@ class TestCommentsViewlet(PloneTestCase): def test_get_commenter_portrait_without_userimage(self): # Create a user without a user image - self.membership_tool.addMember('jim', 'Jim', ['Member'], []) + self.mtool.addMember('jim', 'Jim', ['Member'], []) # Add a conversation with a comment conversation = IConversation(self.portal.doc1)