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!
svn path=/plone.app.discussion/trunk/; revision=39627
This commit is contained in:
parent
a410c72333
commit
ea13020498
@ -4,6 +4,11 @@ Changelog
|
|||||||
1.0b7 (unreleased)
|
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.
|
* Czech translation added.
|
||||||
[naro]
|
[naro]
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from Acquisition import aq_inner
|
from Acquisition import aq_inner
|
||||||
|
|
||||||
|
from AccessControl import Unauthorized
|
||||||
from AccessControl import getSecurityManager
|
from AccessControl import getSecurityManager
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -108,11 +109,13 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
author_email = u""
|
author_email = u""
|
||||||
#author_notification = None
|
#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)
|
registry = queryUtility(IRegistry)
|
||||||
settings = registry.forInterface(IDiscussionSettings)
|
settings = registry.forInterface(IDiscussionSettings)
|
||||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||||
if settings.captcha != 'disabled' and \
|
if settings.captcha != 'disabled' and \
|
||||||
|
settings.anonymous_comments and \
|
||||||
portal_membership.isAnonymousUser():
|
portal_membership.isAnonymousUser():
|
||||||
if not 'captcha' in data:
|
if not 'captcha' in data:
|
||||||
data['captcha'] = u""
|
data['captcha'] = u""
|
||||||
@ -123,13 +126,13 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
None)
|
None)
|
||||||
captcha.validate(data['captcha'])
|
captcha.validate(data['captcha'])
|
||||||
|
|
||||||
|
# Fetch data from request
|
||||||
if 'title' in data:
|
if 'title' in data:
|
||||||
title = data['title']
|
title = data['title']
|
||||||
if 'text' in data:
|
if 'text' in data:
|
||||||
text = data['text']
|
text = data['text']
|
||||||
if 'author_name' in data:
|
if 'author_name' in data:
|
||||||
author_name = data['author_name']
|
author_name = data['author_name']
|
||||||
|
|
||||||
if 'author_email' in data:
|
if 'author_email' in data:
|
||||||
author_email = data['author_email']
|
author_email = data['author_email']
|
||||||
#if 'author_notification' in data:
|
#if 'author_notification' in data:
|
||||||
@ -138,6 +141,11 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
# The add-comment view is called on the conversation object
|
# The add-comment view is called on the conversation object
|
||||||
conversation = IConversation(self.__parent__)
|
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']:
|
if data['in_reply_to']:
|
||||||
# Fetch the comment we want to reply to
|
# Fetch the comment we want to reply to
|
||||||
conversation_to_reply_to = conversation.get(data['in_reply_to'])
|
conversation_to_reply_to = conversation.get(data['in_reply_to'])
|
||||||
@ -150,13 +158,14 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
|
|
||||||
portal_membership = getToolByName(self.context, 'portal_membership')
|
portal_membership = getToolByName(self.context, 'portal_membership')
|
||||||
|
|
||||||
if portal_membership.isAnonymousUser():
|
if portal_membership.isAnonymousUser() and \
|
||||||
|
settings.anonymous_comments:
|
||||||
comment.creator = None
|
comment.creator = None
|
||||||
comment.author_name = author_name
|
comment.author_name = author_name
|
||||||
comment.author_email = author_email
|
comment.author_email = author_email
|
||||||
#comment.author_notification = author_notification
|
#comment.author_notification = author_notification
|
||||||
comment.creation_date = comment.modification_date = datetime.now()
|
comment.creation_date = comment.modification_date = datetime.now()
|
||||||
else:
|
elif not portal_membership.isAnonymousUser():
|
||||||
member = portal_membership.getAuthenticatedMember()
|
member = portal_membership.getAuthenticatedMember()
|
||||||
comment.creator = member.id
|
comment.creator = member.id
|
||||||
comment.author_username = member.getUserName()
|
comment.author_username = member.getUserName()
|
||||||
@ -164,6 +173,9 @@ class CommentForm(extensible.ExtensibleForm, form.Form):
|
|||||||
comment.author_email = member.getProperty('email')
|
comment.author_email = member.getProperty('email')
|
||||||
#comment.author_notification = comment.author_notification
|
#comment.author_notification = comment.author_notification
|
||||||
comment.creation_date = comment.modification_date = datetime.now()
|
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
|
# Check if the added comment is a reply to an existing comment
|
||||||
# or just a regular reply to the content object.
|
# or just a regular reply to the content object.
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from AccessControl import Unauthorized
|
||||||
|
|
||||||
from Acquisition import Implicit
|
from Acquisition import Implicit
|
||||||
|
|
||||||
from zope.component import createObject, queryUtility
|
from zope.component import createObject, queryUtility
|
||||||
@ -23,10 +25,14 @@ from zope.component import getMultiAdapter
|
|||||||
from plone.registry.interfaces import IRegistry
|
from plone.registry.interfaces import IRegistry
|
||||||
|
|
||||||
from Products.CMFCore.utils import getToolByName
|
from Products.CMFCore.utils import getToolByName
|
||||||
|
|
||||||
from Products.CMFPlone.tests import dummy
|
from Products.CMFPlone.tests import dummy
|
||||||
|
|
||||||
from Products.Five.testbrowser import Browser
|
from Products.Five.testbrowser import Browser
|
||||||
|
|
||||||
from Products.PloneTestCase.ptc import PloneTestCase
|
from Products.PloneTestCase.ptc import PloneTestCase
|
||||||
from Products.PloneTestCase.ptc import FunctionalTestCase
|
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.comment import Comment
|
||||||
from plone.app.discussion.browser.comments import CommentsViewlet
|
from plone.app.discussion.browser.comments import CommentsViewlet
|
||||||
@ -43,16 +49,19 @@ class TestCommentForm(PloneTestCase):
|
|||||||
self.loginAsPortalOwner()
|
self.loginAsPortalOwner()
|
||||||
typetool = self.portal.portal_types
|
typetool = self.portal.portal_types
|
||||||
typetool.constructContent('Document', self.portal, 'doc1')
|
typetool.constructContent('Document', self.portal, 'doc1')
|
||||||
self.portal_discussion = getToolByName(self.portal,
|
self.dtool = getToolByName(self.portal,
|
||||||
'portal_discussion',
|
'portal_discussion',
|
||||||
None)
|
None)
|
||||||
self.membership_tool = getToolByName(self.folder, 'portal_membership')
|
self.dtool.overrideDiscussionFor(self.portal.doc1, False)
|
||||||
|
self.mtool = getToolByName(self.folder, 'portal_membership', None)
|
||||||
self.memberdata = self.portal.portal_memberdata
|
self.memberdata = self.portal.portal_memberdata
|
||||||
self.request = self.app.REQUEST
|
self.request = self.app.REQUEST
|
||||||
self.context = getattr(self.portal, 'doc1')
|
self.context = getattr(self.portal, 'doc1')
|
||||||
self.viewlet = CommentsViewlet(self.context, self.request, None, None)
|
|
||||||
|
|
||||||
def test_add_comment(self):
|
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={}):
|
def make_request(form={}):
|
||||||
request = TestRequest()
|
request = TestRequest()
|
||||||
@ -66,6 +75,7 @@ class TestCommentForm(PloneTestCase):
|
|||||||
factory=CommentForm,
|
factory=CommentForm,
|
||||||
name=u"comment-form")
|
name=u"comment-form")
|
||||||
|
|
||||||
|
# The form should return errors if the two required fields are empty
|
||||||
request = make_request(form={})
|
request = make_request(form={})
|
||||||
|
|
||||||
commentForm = getMultiAdapter((self.context, request),
|
commentForm = getMultiAdapter((self.context, request),
|
||||||
@ -76,7 +86,7 @@ class TestCommentForm(PloneTestCase):
|
|||||||
self.assertEquals(len(errors), 2)
|
self.assertEquals(len(errors), 2)
|
||||||
self.failIf(commentForm.handleComment(commentForm, "foo"))
|
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'})
|
request = make_request(form={'form.widgets.text': 'foo'})
|
||||||
|
|
||||||
commentForm = getMultiAdapter((self.context, request),
|
commentForm = getMultiAdapter((self.context, request),
|
||||||
@ -85,8 +95,10 @@ class TestCommentForm(PloneTestCase):
|
|||||||
data, errors = commentForm.extractData()
|
data, errors = commentForm.extractData()
|
||||||
|
|
||||||
self.assertEquals(len(errors), 1)
|
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',
|
request = make_request(form={'form.widgets.title': 'foo',
|
||||||
'form.widgets.text': 'bar'})
|
'form.widgets.text': 'bar'})
|
||||||
|
|
||||||
@ -99,6 +111,76 @@ class TestCommentForm(PloneTestCase):
|
|||||||
self.failIf(commentForm.handleComment(commentForm, "foo"))
|
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):
|
class TestCommentsViewletIntegration(FunctionalTestCase):
|
||||||
|
|
||||||
layer = DiscussionLayer
|
layer = DiscussionLayer
|
||||||
@ -108,8 +190,6 @@ class TestCommentsViewletIntegration(FunctionalTestCase):
|
|||||||
portal_url = self.portal.absolute_url()
|
portal_url = self.portal.absolute_url()
|
||||||
browser.handleErrors = False
|
browser.handleErrors = False
|
||||||
|
|
||||||
from Products.PloneTestCase.setup import portal_owner, default_password
|
|
||||||
|
|
||||||
browser.open(portal_url + '/login_form')
|
browser.open(portal_url + '/login_form')
|
||||||
browser.getControl(name='__ac_name').value = portal_owner
|
browser.getControl(name='__ac_name').value = portal_owner
|
||||||
browser.getControl(name='__ac_password').value = default_password
|
browser.getControl(name='__ac_password').value = default_password
|
||||||
@ -151,7 +231,7 @@ class TestCommentsViewlet(PloneTestCase):
|
|||||||
self.portal_discussion = getToolByName(self.portal,
|
self.portal_discussion = getToolByName(self.portal,
|
||||||
'portal_discussion',
|
'portal_discussion',
|
||||||
None)
|
None)
|
||||||
self.membership_tool = getToolByName(self.folder, 'portal_membership')
|
self.mtool = getToolByName(self.folder, 'portal_membership')
|
||||||
self.memberdata = self.portal.portal_memberdata
|
self.memberdata = self.portal.portal_memberdata
|
||||||
request = self.app.REQUEST
|
request = self.app.REQUEST
|
||||||
context = getattr(self.portal, 'doc1')
|
context = getattr(self.portal, 'doc1')
|
||||||
@ -265,7 +345,7 @@ class TestCommentsViewlet(PloneTestCase):
|
|||||||
def test_get_commenter_portrait(self):
|
def test_get_commenter_portrait(self):
|
||||||
|
|
||||||
# Add a user with a member image
|
# 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',
|
self.memberdata._setPortrait(Image(id='jim',
|
||||||
file=dummy.File(),
|
file=dummy.File(),
|
||||||
title=''), 'jim')
|
title=''), 'jim')
|
||||||
@ -298,7 +378,7 @@ class TestCommentsViewlet(PloneTestCase):
|
|||||||
def test_get_commenter_portrait_without_userimage(self):
|
def test_get_commenter_portrait_without_userimage(self):
|
||||||
|
|
||||||
# Create a user without a user image
|
# 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
|
# Add a conversation with a comment
|
||||||
conversation = IConversation(self.portal.doc1)
|
conversation = IConversation(self.portal.doc1)
|
||||||
|
Loading…
Reference in New Issue
Block a user