Merge lp:~sinzui/launchpad/hide-question-comment into lp:launchpad

Proposed by Curtis Hovey on 2012-10-30
Status: Merged
Approved by: j.c.sackett on 2012-10-30
Approved revision: no longer in the source branch.
Merged at revision: 16216
Proposed branch: lp:~sinzui/launchpad/hide-question-comment
Merge into: lp:launchpad
Diff against target: 280 lines (+62/-38)
7 files modified
lib/lp/answers/browser/question.py (+4/-2)
lib/lp/answers/browser/tests/test_questionmessages.py (+20/-3)
lib/lp/answers/configure.zcml (+2/-5)
lib/lp/answers/doc/faq.txt (+1/-1)
lib/lp/answers/model/question.py (+8/-1)
lib/lp/answers/tests/test_question_webservice.py (+16/-17)
lib/lp/security.py (+11/-9)
To merge this branch: bzr merge lp:~sinzui/launchpad/hide-question-comment
Reviewer Review Type Date Requested Status
j.c.sackett (community) 2012-10-30 Approve on 2012-10-30
Review via email: mp+132177@code.launchpad.net

Commit Message

Allow users to hide their question comments.

Description of the Change

Users can hide their bug comments, but not their question comment.
The QuestionMessageDisplayView.canSeeSpamControls checks if the
user has launchpad.Moderate on the question, which users do not have.
The test must check the comment.

--------------------------------------------------------------------

RULES

    Pre-implementation: no one
    * security.SetQuestionCommentVisibility (launchpad.Moderate) only
      allows ~admins and ~registry
      * The question owner check is missing.
      * IQuestion is checked, but IQuestionComment is the context.
    * Change the checker to be for IQuestionMessage and include
      the comment owner in th check.
    * Revise or remove SetQuestionCommentVisibility because the permission
      does not have enough information to know if the user is the comment
      owner. Maybe move the permission check into setCommentVisibility()
      as done by the rules to delete bug tasks.

QA

    As a non-priv person (non-project maintainer and not ~registry)
    * Visit https://answers.qastaging.launchpad.net/launchpad/+question/188577
    * Verify you can add a comment.
    * Verify you can hide the comment after you reload the page.
    * Verify you can unhide the comment.

LINT

    lib/lp/security.py
    lib/lp/answers/configure.zcml
    lib/lp/answers/browser/question.py
    lib/lp/answers/browser/tests/test_questionmessages.py
    lib/lp/answers/doc/faq.txt
    lib/lp/answers/model/question.py
    lib/lp/answers/tests/test_question_webservice.py

TEST

    ./bin/test -vvc lp.answers.browser.tests.test_questionmessage
    ./bin/test -vvc -t Comment lp.answers.tests.test_question_webservice

LoC

    This is feature work...maybe the last disclosure bug that needs to
    be fixed.

IMPLEMENTATION

Fixed the doctest to reflect the permission used by the code.
    lib/lp/answers/doc/faq.txt

Ensure comment owners can see their hidden comments and the hide/unhide
controls. Tests should *never* use admins as the creator of data -- no
admin asks a question in Lp.
    lib/lp/security.py
    lib/lp/answers/browser/question.py
    lib/lp/answers/browser/tests/test_questionmessages.py

Removed SetQuestionCommentVisibility because the argument is what
determines permission to IQuestionMessage.message.visibility. Moved
setCommentVisibility() into the methods anyone can call, but added a
launchpad.Moderate check on the QuestionMessage to ensure access is
still guarded. Admins do not ask questions...set the test up as
questions are really used.
    lib/lp/security.py
    lib/lp/answers/configure.zcml
    lib/lp/answers/model/question.py
    lib/lp/answers/tests/test_question_webservice.py

To post a comment you must log in.
j.c.sackett (jcsackett) wrote :

Looks good, Curtis. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/answers/browser/question.py'
2--- lib/lp/answers/browser/question.py 2012-02-21 22:46:28 +0000
3+++ lib/lp/answers/browser/question.py 2012-10-30 17:38:23 +0000
4@@ -923,7 +923,9 @@
5 role = PersonRoles(self.user)
6 strip_invisible = not (role.in_admin or role.in_registry_experts)
7 if strip_invisible:
8- messages = [message for message in messages if message.visible]
9+ messages = [
10+ message for message in messages
11+ if message.visible or message.owner == self.user]
12 return messages
13
14 def canAddComment(self, action):
15@@ -1143,7 +1145,7 @@
16
17 @cachedproperty
18 def canSeeSpamControls(self):
19- return check_permission('launchpad.Moderate', self.context.question)
20+ return check_permission('launchpad.Moderate', self.context)
21
22 def getBoardCommentCSSClass(self):
23 css_classes = ["boardComment"]
24
25=== modified file 'lib/lp/answers/browser/tests/test_questionmessages.py'
26--- lib/lp/answers/browser/tests/test_questionmessages.py 2012-01-01 02:58:52 +0000
27+++ lib/lp/answers/browser/tests/test_questionmessages.py 2012-10-30 17:38:23 +0000
28@@ -18,6 +18,7 @@
29 person_logged_in,
30 )
31 from lp.testing.layers import DatabaseFunctionalLayer
32+from lp.testing.pages import find_tag_by_id
33
34
35 class TestQuestionMessageVisibility(
36@@ -28,9 +29,10 @@
37 def makeHiddenMessage(self):
38 """Required by the mixin."""
39 administrator = getUtility(ILaunchpadCelebrities).admin.teamowner
40+ self.commenter = self.factory.makePerson()
41 with person_logged_in(administrator):
42 question = self.factory.makeQuestion()
43- comment = question.addComment(administrator, self.comment_text)
44+ comment = question.addComment(self.commenter, self.comment_text)
45 removeSecurityProxy(comment).message.visible = False
46 return question
47
48@@ -42,6 +44,12 @@
49 no_login=no_login)
50 return view
51
52+ def test_commenter_can_see_comments(self):
53+ # The author of the comment can see the hidden comment.
54+ context = self.makeHiddenMessage()
55+ view = self.getView(context=context, user=self.commenter)
56+ self.assertIn(self.comment_text, view.contents)
57+
58
59 class TestHideQuestionMessageControls(
60 BrowserTestCase, TestHideMessageControlMixin):
61@@ -53,10 +61,11 @@
62 def getContext(self, comment_owner=None):
63 """Required by the mixin."""
64 administrator = getUtility(ILaunchpadCelebrities).admin.teamowner
65+ user = comment_owner or administrator
66 question = self.factory.makeQuestion()
67 body = self.factory.getUniqueString()
68- with person_logged_in(administrator):
69- question.addComment(administrator, body)
70+ with person_logged_in(user):
71+ question.addComment(user, body)
72 return question
73
74 def getView(self, context, user=None, no_login=False):
75@@ -66,3 +75,11 @@
76 user=user,
77 no_login=no_login)
78 return view
79+
80+ def test_comment_owner_sees_hide_control(self):
81+ # The comment owner sees the hide control.
82+ user = self.factory.makePerson()
83+ context = self.getContext(comment_owner=user)
84+ view = self.getView(context=context, user=user)
85+ hide_link = find_tag_by_id(view.contents, self.control_text)
86+ self.assertIsNot(None, hide_link)
87
88=== modified file 'lib/lp/answers/configure.zcml'
89--- lib/lp/answers/configure.zcml 2011-12-24 17:49:30 +0000
90+++ lib/lp/answers/configure.zcml 2012-10-30 17:38:23 +0000
91@@ -110,7 +110,8 @@
92 <require
93 permission="launchpad.AnyPerson"
94 attributes="requestInfo giveAnswer expireQuestion addComment
95- addCommentWithoutNotify linkFAQ subscribe unsubscribe"
96+ addCommentWithoutNotify linkFAQ subscribe unsubscribe
97+ setCommentVisibility"
98 set_attributes="title description datedue target language"
99 />
100 <require
101@@ -119,10 +120,6 @@
102 set_attributes="assignee"
103 />
104 <require
105- permission="launchpad.Moderate"
106- attributes="setCommentVisibility"
107- />
108- <require
109 permission="launchpad.Admin"
110 attributes="setStatus"
111 set_attributes="priority whiteboard"
112
113=== modified file 'lib/lp/answers/doc/faq.txt'
114--- lib/lp/answers/doc/faq.txt 2012-07-27 13:12:41 +0000
115+++ lib/lp/answers/doc/faq.txt 2012-10-30 17:38:23 +0000
116@@ -29,7 +29,7 @@
117 >>> verifyObject(IFAQTarget, removeSecurityProxy(ubuntu))
118 True
119
120-Any user who has 'launchpad.Moderate' on the project can create a new
121+Any user who has 'launchpad.Append' on the project can create a new
122 FAQ. (That permission is granted to project's registrant and answer
123 contacts.)
124
125
126=== modified file 'lib/lp/answers/model/question.py'
127--- lib/lp/answers/model/question.py 2012-07-27 13:12:41 +0000
128+++ lib/lp/answers/model/question.py 2012-10-30 17:38:23 +0000
129@@ -43,6 +43,7 @@
130 implements,
131 providedBy,
132 )
133+from zope.security.interfaces import Unauthorized
134 from zope.security.proxy import isinstance as zope_isinstance
135
136 from lp.answers.enums import (
137@@ -113,6 +114,7 @@
138 MessageChunk,
139 )
140 from lp.services.propertycache import cachedproperty
141+from lp.services.webapp.authorization import check_permission
142 from lp.services.worlddata.helpers import is_english_variant
143 from lp.services.worlddata.interfaces.language import ILanguage
144 from lp.services.worlddata.model.language import Language
145@@ -680,7 +682,12 @@
146
147 def setCommentVisibility(self, user, comment_number, visible):
148 """See `IQuestion`."""
149- message = self.messages[comment_number].message
150+ question_message = self.messages[comment_number]
151+ if not check_permission('launchpad.Moderate', question_message):
152+ raise Unauthorized(
153+ "Only admins, project maintainers, and comment authors "
154+ "can change a comment's visibility.")
155+ message = question_message.message
156 message.visible = visible
157
158
159
160=== modified file 'lib/lp/answers/tests/test_question_webservice.py'
161--- lib/lp/answers/tests/test_question_webservice.py 2012-10-09 10:28:02 +0000
162+++ lib/lp/answers/tests/test_question_webservice.py 2012-10-30 17:38:23 +0000
163@@ -9,7 +9,6 @@
164 from lazr.restfulclient.errors import HTTPError
165 from simplejson import dumps
166 import transaction
167-from zope.component import getUtility
168 from zope.security.proxy import removeSecurityProxy
169
170 from lp.answers.errors import (
171@@ -21,7 +20,6 @@
172 NotQuestionOwnerError,
173 QuestionTargetError,
174 )
175-from lp.registry.interfaces.person import IPersonSet
176 from lp.testing import (
177 celebrity_logged_in,
178 launchpadlib_for,
179@@ -144,13 +142,11 @@
180
181 def setUp(self):
182 super(TestSetCommentVisibility, self).setUp()
183- self.person_set = getUtility(IPersonSet)
184- admins = self.person_set.getByName('admins')
185- self.admin = admins.teamowner
186- with person_logged_in(self.admin):
187+ self.commenter = self.factory.makePerson()
188+ with person_logged_in(self.commenter):
189 self.question = self.factory.makeQuestion()
190 self.message = self.question.addComment(
191- self.admin, 'Some comment')
192+ self.commenter, 'Some comment')
193 transaction.commit()
194
195 def _get_question_for_user(self, user=None):
196@@ -169,8 +165,8 @@
197 def test_random_user_cannot_set_visible(self):
198 # Logged in users without privs can't set question comment
199 # visibility.
200- nopriv = self.person_set.getByName('no-priv')
201- question = self._get_question_for_user(nopriv)
202+ random_user = self.factory.makePerson()
203+ question = self._get_question_for_user(random_user)
204 self.assertRaises(
205 HTTPError,
206 self._set_visibility,
207@@ -185,13 +181,18 @@
208 self._set_visibility,
209 question)
210
211+ def test_comment_owner_can_set_visible(self):
212+ # Members of registry experts can set question comment
213+ # visibility.
214+ question = self._get_question_for_user(self.commenter)
215+ self._set_visibility(question)
216+ self.assertFalse(self.message.visible)
217+
218 def test_registry_admin_can_set_visible(self):
219 # Members of registry experts can set question comment
220 # visibility.
221- registry = self.person_set.getByName('registry')
222- person = self.factory.makePerson()
223- with person_logged_in(registry.teamowner):
224- registry.addMember(person, registry.teamowner)
225+ with celebrity_logged_in('registry_experts') as registry:
226+ person = registry
227 question = self._get_question_for_user(person)
228 self._set_visibility(question)
229 self.assertFalse(self.message.visible)
230@@ -199,10 +200,8 @@
231 def test_admin_can_set_visible(self):
232 # Admins can set question comment
233 # visibility.
234- admins = self.person_set.getByName('admins')
235- person = self.factory.makePerson()
236- with person_logged_in(admins.teamowner):
237- admins.addMember(person, admins.teamowner)
238+ with celebrity_logged_in('admin') as admin:
239+ person = admin
240 question = self._get_question_for_user(person)
241 self._set_visibility(question)
242 self.assertFalse(self.message.visible)
243
244=== modified file 'lib/lp/security.py'
245--- lib/lp/security.py 2012-10-22 13:53:11 +0000
246+++ lib/lp/security.py 2012-10-30 17:38:23 +0000
247@@ -1983,15 +1983,6 @@
248 return objects
249
250
251-class SetQuestionCommentVisibility(AuthorizationBase):
252- permission = 'launchpad.Moderate'
253- usedfor = IQuestion
254-
255- def checkAuthenticated(self, user):
256- """Admins and registry admins can set bug comment visibility."""
257- return (user.in_admin or user.in_registry_experts)
258-
259-
260 class AdminQuestion(AdminByAdminsTeam):
261 permission = 'launchpad.Admin'
262 usedfor = IQuestion
263@@ -2043,6 +2034,17 @@
264 usedfor = IQuestionMessage
265
266
267+class ModerateQuestionMessage(AuthorizationBase):
268+ permission = 'launchpad.Moderate'
269+ usedfor = IQuestionMessage
270+
271+ def checkAuthenticated(self, user):
272+ """Admins, Registry, Maintainers, and comment owners can moderate."""
273+ return (user.in_admin or user.in_registry_experts
274+ or user.inTeam(self.obj.owner)
275+ or user.inTeam(self.obj.question.target.owner))
276+
277+
278 class AppendFAQTarget(EditByOwnersOrAdmins):
279 permission = 'launchpad.Append'
280 usedfor = IFAQTarget