launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05539
[Merge] lp:~wallyworld/launchpad/hide-bug-comment-permissions-885672 into lp:launchpad
Ian Booth has proposed merging lp:~wallyworld/launchpad/hide-bug-comment-permissions-885672 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #885672 in Launchpad itself: "Users make bugs private because they cannot hide comments"
https://bugs.launchpad.net/launchpad/+bug/885672
For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/hide-bug-comment-permissions-885672/+merge/82360
Allow users in project roles and the comment owner to hide/unhide bug comments.
== Implementation ==
There were existing security adaptors for IBug and IMessage which set launchpad.Admin permissions for admin and registry admin celebrities. When the comment view is rendered, this permission was checked and a 'show_span_controls' boolean was set accordingly. For the API, the setCommentVisible() method was set to required launchpad.Admin as was the 'visible' attribute on the Message itself.
A new method in IBug is introduced - userCanSetCommentVisibility(user). This returns true if the specified user can update the comment visibility and encapsulates the functionality to check that the user is in any of the project roles which allow a comment visibility to be set. This check is done for the pillars of the bug's tasks and is global to the bug. When each comment is rendered, a check is also done so see if the user matches the comment owner.
A common pattern in LP is to encapsulate such permission checks in a method on the model object and call this from the security adaptor. In this case, the security adaptors mentioned earlier are no longer suitable since the adaptors are defined for IBug and the overall permission check needs to include a check for each individual bug comment. Similarly, the setCommentVisible() method is now set to require launchpad.Edit permissions to stop the web service call from being rejected when the bug comment owner invokes the API. The setCommentVisible() method does the required checks and raises an Unauthorised exception if required. Removing the security adaptors is also required since invoking checkPermission() from model code is verboten.
IPersonRoles was extended to provide isBugSupervisor() and isSecurityContact() methods used by userCanSetCommentVisibility()
Having IPersonRoles (in registry) reference IhasBugSupervisor and IHasSecurityContact (both in bugs) is naughty. Both the latter interfaces should be moved to registry or another solution used to fix the layer violation.
== Tests ==
Add tests to TestPersonRoles:
- test_isBugSupervisor
- test_isSecurityContact
New test case in test_bug_messages.py: TestUserCanSetCommentVisibility (test permission checking)
- test_random_user_cannot_toggle_comment_visibility
- test_registry_admin_can_toggle_comment_visibility
- test_admin_can_toggle_comment_visibility
- test_pillar_owner_can_toggle_comment_visibility
- test_pillar_driver_can_toggle_comment_visibility
- test_pillar_bug_supervisor_can_toggle_comment_visibility
- test_pillar_security_contact_can_toggle_comment_visibility
Add tests to TestSetCommentVisibility in test_bug_messages_webservice.py (test comment hiding via web service)
- test_pillar_owner_can_set_visible
- test_pillar_driver_can_set_visible
- test_pillar_bug_supervisor_can_set_visible
- test_pillar_security_contact_can_set_visible
- test_comment_owner_can_set_visible
Add tests to TestBugHideCommentControls in test_bugcomment.py (test hide link rendering)
- test_comment_owner_sees_hide_control
- test_pillar_owner_sees_hide_control
- test_pillar_driver_sees_hide_control
- test_pillar_bug_supervisor_sees_hide_control
- test_pillar_security_contact_sees_hide_control
NB the above tests can be moved to the base view mixin once questions also support the ability for users in project roles to hide comments.
== Lint ==
Checking for conflicts and issues in changed files.
Linting changed files:
lib/canonical/launchpad/security.py
lib/lp/answers/browser/tests/test_questionmessages.py
lib/lp/bugs/configure.zcml
lib/lp/bugs/security.py
lib/lp/bugs/browser/bugcomment.py
lib/lp/bugs/browser/bugtask.py
lib/lp/bugs/browser/tests/test_bugcomment.py
lib/lp/bugs/interfaces/bug.py
lib/lp/bugs/model/bug.py
lib/lp/bugs/tests/test_bug_messages.py
lib/lp/bugs/tests/test_bug_messages_webservice.py
lib/lp/coop/answersbugs/visibility.py
lib/lp/registry/interfaces/role.py
lib/lp/registry/model/personroles.py
lib/lp/registry/tests/test_personroles.py
lib/lp/services/messages/configure.zcml
--
https://code.launchpad.net/~wallyworld/launchpad/hide-bug-comment-permissions-885672/+merge/82360
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/hide-bug-comment-permissions-885672 into lp:launchpad.
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2011-09-07 21:53:15 +0000
+++ lib/canonical/launchpad/security.py 2011-11-16 07:32:26 +0000
@@ -157,7 +157,6 @@
from lp.registry.interfaces.sourcepackage import ISourcePackage
from lp.registry.interfaces.teammembership import ITeamMembership
from lp.registry.interfaces.wikiname import IWikiName
-from lp.services.messages.interfaces.message import IMessage
from lp.services.openid.interfaces.openididentifier import IOpenIdIdentifier
from lp.services.worlddata.interfaces.country import ICountry
from lp.services.worlddata.interfaces.language import (
@@ -2567,15 +2566,6 @@
return self.forwardCheckAuthenticated(user, parent)
-class SetMessageVisibility(AuthorizationBase):
- permission = 'launchpad.Admin'
- usedfor = IMessage
-
- def checkAuthenticated(self, user):
- """Admins and registry admins can set bug comment visibility."""
- return (user.in_admin or user.in_registry_experts)
-
-
class ViewPublisherConfig(AdminByAdminsTeam):
usedfor = IPublisherConfig
=== modified file 'lib/lp/answers/browser/tests/test_questionmessages.py'
--- lib/lp/answers/browser/tests/test_questionmessages.py 2011-05-27 21:12:25 +0000
+++ lib/lp/answers/browser/tests/test_questionmessages.py 2011-11-16 07:32:26 +0000
@@ -50,7 +50,7 @@
control_text = 'mark-spam-0'
- def getContext(self):
+ def getContext(self, comment_owner=None):
"""Required by the mixin."""
administrator = getUtility(ILaunchpadCelebrities).admin.teamowner
question = self.factory.makeQuestion()
=== modified file 'lib/lp/bugs/browser/bugcomment.py'
--- lib/lp/bugs/browser/bugcomment.py 2011-09-15 16:31:49 +0000
+++ lib/lp/bugs/browser/bugcomment.py 2011-11-16 07:32:26 +0000
@@ -52,7 +52,8 @@
def build_comments_from_chunks(
- bugtask, truncate=False, slice_info=None, show_spam_controls=False):
+ bugtask, truncate=False, slice_info=None, show_spam_controls=False,
+ user=None):
"""Build BugComments from MessageChunks.
:param truncate: Perform truncation of large messages.
@@ -66,7 +67,7 @@
if bug_comment is None:
bug_comment = BugComment(
bugmessage.index, message, bugtask, visible=message.visible,
- show_spam_controls=show_spam_controls)
+ show_spam_controls=show_spam_controls, user=user)
comments[message.id] = bug_comment
# This code path is currently only used from a BugTask view which
# has already loaded all the bug watches. If we start lazy loading
@@ -176,7 +177,7 @@
def __init__(
self, index, message, bugtask, activity=None,
- visible=True, show_spam_controls=False):
+ visible=True, show_spam_controls=False, user=None):
self.index = index
self.bugtask = bugtask
@@ -199,7 +200,8 @@
self.synchronized = False
self.visible = visible
- self.show_spam_controls = show_spam_controls
+ user_owns_comment = user is not None and user == self.owner
+ self.show_spam_controls = show_spam_controls or user_owns_comment
@property
def show_for_admin(self):
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2011-11-14 17:57:46 +0000
+++ lib/lp/bugs/browser/bugtask.py 2011-11-16 07:32:26 +0000
@@ -329,7 +329,7 @@
def get_comments_for_bugtask(bugtask, truncate=False, for_display=False,
- slice_info=None, show_spam_controls=False):
+ slice_info=None, show_spam_controls=False, user=None):
"""Return BugComments related to a bugtask.
This code builds a sorted list of BugComments in one shot,
@@ -342,7 +342,8 @@
to retrieve.
"""
comments = build_comments_from_chunks(bugtask, truncate=truncate,
- slice_info=slice_info, show_spam_controls=show_spam_controls)
+ slice_info=slice_info, show_spam_controls=show_spam_controls,
+ user=user)
# TODO: further fat can be shaved off here by limiting the attachments we
# query to those that slice_info would include.
for attachment in bugtask.bug.attachments_unpopulated:
@@ -749,11 +750,12 @@
return self._getComments()
def _getComments(self, slice_info=None):
- show_spam_controls = check_permission(
- 'launchpad.Admin', self.context.bug)
+ bug = self.context.bug
+ show_spam_controls = bug.userCanSetCommentVisibility(self.user)
return get_comments_for_bugtask(
self.context, truncate=True, slice_info=slice_info,
- for_display=True, show_spam_controls=show_spam_controls)
+ for_display=True, show_spam_controls=show_spam_controls,
+ user=self.user)
@cachedproperty
def interesting_activity(self):
=== modified file 'lib/lp/bugs/browser/tests/test_bugcomment.py'
--- lib/lp/bugs/browser/tests/test_bugcomment.py 2011-05-27 21:12:25 +0000
+++ lib/lp/bugs/browser/tests/test_bugcomment.py 2011-11-16 07:32:26 +0000
@@ -13,7 +13,9 @@
from pytz import utc
from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
+from canonical.launchpad.testing.pages import find_tag_by_id
from canonical.testing.layers import DatabaseFunctionalLayer
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.bugs.browser.bugcomment import group_comments_with_activity
@@ -220,12 +222,12 @@
layer = DatabaseFunctionalLayer
- def getContext(self):
+ def getContext(self, comment_owner=None):
"""Required by the mixin."""
administrator = getUtility(ILaunchpadCelebrities).admin.teamowner
bug = self.factory.makeBug()
with person_logged_in(administrator):
- self.factory.makeBugComment(bug=bug)
+ self.factory.makeBugComment(bug=bug, owner=comment_owner)
return bug
def getView(self, context, user=None, no_login=False):
@@ -235,3 +237,51 @@
user=user,
no_login=no_login)
return view
+
+ def test_comment_owner_sees_hide_control(self):
+ # The comment owner sees the hide control.
+ owner = self.factory.makePerson()
+ context = self.getContext(comment_owner=owner)
+ view = self.getView(context=context, user=owner)
+ hide_link = find_tag_by_id(view.contents, self.control_text)
+ self.assertIsNot(None, hide_link)
+
+ def test_pillar_owner_sees_hide_control(self):
+ # The pillar owner sees the hide control.
+ person = self.factory.makePerson()
+ context = self.getContext()
+ naked_bugtask = removeSecurityProxy(context.default_bugtask)
+ removeSecurityProxy(naked_bugtask.pillar).owner = person
+ view = self.getView(context=context, user=person)
+ hide_link = find_tag_by_id(view.contents, self.control_text)
+ self.assertIsNot(None, hide_link)
+
+ def test_pillar_driver_sees_hide_control(self):
+ # The pillar driver sees the hide control.
+ person = self.factory.makePerson()
+ context = self.getContext()
+ naked_bugtask = removeSecurityProxy(context.default_bugtask)
+ removeSecurityProxy(naked_bugtask.pillar).driver = person
+ view = self.getView(context=context, user=person)
+ hide_link = find_tag_by_id(view.contents, self.control_text)
+ self.assertIsNot(None, hide_link)
+
+ def test_pillar_bug_supervisor_sees_hide_control(self):
+ # The pillar bug supervisor sees the hide control.
+ person = self.factory.makePerson()
+ context = self.getContext()
+ naked_bugtask = removeSecurityProxy(context.default_bugtask)
+ removeSecurityProxy(naked_bugtask.pillar).bug_supervisor = person
+ view = self.getView(context=context, user=person)
+ hide_link = find_tag_by_id(view.contents, self.control_text)
+ self.assertIsNot(None, hide_link)
+
+ def test_pillar_security_contact_sees_hide_control(self):
+ # The pillar security contact sees the hide control.
+ person = self.factory.makePerson()
+ context = self.getContext()
+ naked_bugtask = removeSecurityProxy(context.default_bugtask)
+ removeSecurityProxy(naked_bugtask.pillar).security_contact = person
+ view = self.getView(context=context, user=person)
+ hide_link = find_tag_by_id(view.contents, self.control_text)
+ self.assertIsNot(None, hide_link)
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2011-11-14 00:11:41 +0000
+++ lib/lp/bugs/configure.zcml 2011-11-16 07:32:26 +0000
@@ -746,6 +746,7 @@
who_made_private
date_made_private
userCanView
+ userCanSetCommentVisibility
personIsDirectSubscriber
personIsAlsoNotifiedSubscriber
personIsSubscribedToDuplicate
@@ -849,6 +850,7 @@
mute
newMessage
removeWatch
+ setCommentVisibility
setPrivate
setSecurityRelated
setPrivacyAndSecurityRelated
@@ -882,7 +884,6 @@
<require
permission="launchpad.Admin"
attributes="
- setCommentVisibility
setHeat"
set_attributes="heat_last_updated" />
</class>
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2011-11-14 00:29:56 +0000
+++ lib/lp/bugs/interfaces/bug.py 2011-11-16 07:32:26 +0000
@@ -944,6 +944,9 @@
def userCanView(user):
"""Return True if `user` can see this IBug, false otherwise."""
+ def userCanSetCommentVisibility(user):
+ """Return True if `user` can set bug comment visibility."""
+
@operation_parameters(
submission=Reference(
Interface, title=_('A HWDB submission'), required=True))
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2011-11-14 18:35:39 +0000
+++ lib/lp/bugs/model/bug.py 2011-11-16 07:32:26 +0000
@@ -80,6 +80,7 @@
implements,
providedBy,
)
+from zope.security.interfaces import Unauthorized
from zope.security.proxy import (
ProxyFactory,
removeSecurityProxy,
@@ -191,6 +192,7 @@
validate_person,
validate_public_person,
)
+from lp.registry.interfaces.role import IPersonRoles
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.productseries import IProductSeries
from lp.registry.interfaces.series import SeriesStatus
@@ -2081,6 +2083,10 @@
bug_message_set = getUtility(IBugMessageSet)
bug_message = bug_message_set.getByBugAndMessage(
self, self.messages[comment_number])
+ if (not self.userCanSetCommentVisibility(user)
+ and bug_message.owner != user):
+ raise Unauthorized(
+ "User %s cannot hide or show bug comments" % user.name)
bug_message.message.visible = visible
@cachedproperty
@@ -2131,6 +2137,44 @@
return True
return False
+ def userCanSetCommentVisibility(self, user):
+ """See `IBug`.
+
+ This method is called by security adapters for authenticated users.
+
+ Users who can set bug comment visibility are:
+ - Admins and registry admins
+ - users in project roles on any bugtask:
+ - maintainer
+ - driver
+ - bug supervisor
+ - security contact
+
+ Additionally, the comment owners can hide their own comments but that
+ is not checked here - this method is to see if arbitrary users can
+ hide comments they did not make themselves.
+ """
+
+ if user is None:
+ return False
+ roles = IPersonRoles(user)
+ if roles.in_admin or roles.in_registry_experts:
+ return True
+ return self.userInProjectRole(roles)
+
+ def userInProjectRole(self, user):
+ """ Return True if user has a project role for any affected pillar."""
+ roles = IPersonRoles(user)
+ if roles is None:
+ return False
+ for pillar in self.affected_pillars:
+ if (roles.isOwner(pillar)
+ or roles.isOneOfDrivers(pillar)
+ or roles.isBugSupervisor(pillar)
+ or roles.isSecurityContact(pillar)):
+ return True
+ return False
+
def linkHWSubmission(self, submission):
"""See `IBug`."""
getUtility(IHWSubmissionBugSet).create(submission, self)
=== modified file 'lib/lp/bugs/security.py'
--- lib/lp/bugs/security.py 2011-10-26 02:00:58 +0000
+++ lib/lp/bugs/security.py 2011-11-16 07:32:26 +0000
@@ -210,15 +210,6 @@
usedfor = IMessage
-class SetBugCommentVisibility(AuthorizationBase):
- permission = 'launchpad.Admin'
- usedfor = IBug
-
- def checkAuthenticated(self, user):
- """Admins and registry admins can set bug comment visibility."""
- return (user.in_admin or user.in_registry_experts)
-
-
class ViewBugTracker(AnonymousAuthorization):
"""Anyone can view a bug tracker."""
usedfor = IBugTracker
=== modified file 'lib/lp/bugs/tests/test_bug_messages.py'
--- lib/lp/bugs/tests/test_bug_messages.py 2011-02-14 00:15:22 +0000
+++ lib/lp/bugs/tests/test_bug_messages.py 2011-11-16 07:32:26 +0000
@@ -5,9 +5,18 @@
__metaclass__ = type
+from zope.component import getUtility
+
from canonical.launchpad.ftests import login
-from canonical.testing.layers import DatabaseFunctionalLayer
-from lp.testing import TestCaseWithFactory
+from canonical.testing.layers import (
+ DatabaseFunctionalLayer,
+ LaunchpadFunctionalLayer,
+ )
+from lp.registry.interfaces.person import IPersonSet
+from lp.testing import (
+ person_logged_in,
+ TestCaseWithFactory,
+ )
class TestBugIndexedMessages(TestCaseWithFactory):
@@ -35,3 +44,64 @@
# IIndexedMessage.
for indexed_message in self.bug_2.indexed_messages:
self.failUnlessEqual(None, indexed_message.parent)
+
+
+class TestUserCanSetCommentVisibility(TestCaseWithFactory):
+
+ """Test whether expected users can toggle bug comment visibility."""
+
+ layer = LaunchpadFunctionalLayer
+
+ def test_random_user_cannot_toggle_comment_visibility(self):
+ # A random user cannot set bug comment visibility.
+ person = self.factory.makePerson()
+ bug = self.factory.makeBug()
+ self.assertFalse(bug.userCanSetCommentVisibility(person))
+
+ def test_registry_admin_can_toggle_comment_visibility(self):
+ # Members of registry experts can set bug comment visibility.
+ person_set = getUtility(IPersonSet)
+ registry = person_set.getByName('registry')
+ person = self.factory.makePerson()
+ with person_logged_in(registry.teamowner):
+ registry.addMember(person, registry.teamowner)
+ bug = self.factory.makeBug()
+ self.assertTrue(bug.userCanSetCommentVisibility(person))
+
+ def test_admin_can_toggle_comment_visibility(self):
+ # Admins can set bug comment visibility.
+ person_set = getUtility(IPersonSet)
+ admins = person_set.getByName('admins')
+ person = self.factory.makePerson()
+ with person_logged_in(admins.teamowner):
+ admins.addMember(person, admins.teamowner)
+ bug = self.factory.makeBug()
+ self.assertTrue(bug.userCanSetCommentVisibility(person))
+
+ def test_pillar_owner_can_toggle_comment_visibility(self):
+ # Pillar owner can set bug comment visibility.
+ person = self.factory.makePerson()
+ product = self.factory.makeProduct(owner=person)
+ bug = self.factory.makeBug(product=product)
+ self.assertTrue(bug.userCanSetCommentVisibility(person))
+
+ def test_pillar_driver_can_toggle_comment_visibility(self):
+ # Pillar driver can set bug comment visibility.
+ person = self.factory.makePerson()
+ product = self.factory.makeProduct(driver=person)
+ bug = self.factory.makeBug(product=product)
+ self.assertTrue(bug.userCanSetCommentVisibility(person))
+
+ def test_pillar_bug_supervisor_can_toggle_comment_visibility(self):
+ # Pillar bug supervisor can set bug comment visibility.
+ person = self.factory.makePerson()
+ product = self.factory.makeProduct(bug_supervisor=person)
+ bug = self.factory.makeBug(product=product)
+ self.assertTrue(bug.userCanSetCommentVisibility(person))
+
+ def test_pillar_security_contact_can_toggle_comment_visibility(self):
+ # Pillar security contact can set bug comment visibility.
+ person = self.factory.makePerson()
+ product = self.factory.makeProduct(security_contact=person)
+ bug = self.factory.makeBug(product=product)
+ self.assertTrue(bug.userCanSetCommentVisibility(person))
=== modified file 'lib/lp/bugs/tests/test_bug_messages_webservice.py'
--- lib/lp/bugs/tests/test_bug_messages_webservice.py 2011-04-05 22:34:35 +0000
+++ lib/lp/bugs/tests/test_bug_messages_webservice.py 2011-11-16 07:32:26 +0000
@@ -10,11 +10,9 @@
from lazr.restfulclient.errors import HTTPError
from zope.component import getUtility
from zope.security.management import endInteraction
+from zope.security.proxy import removeSecurityProxy
-from canonical.testing.layers import (
- DatabaseFunctionalLayer,
- LaunchpadFunctionalLayer,
- )
+from canonical.testing.layers import DatabaseFunctionalLayer
from lp.bugs.interfaces.bugmessage import IBugMessageSet
from lp.registry.interfaces.person import IPersonSet
from lp.testing import (
@@ -133,3 +131,47 @@
bug = self._get_bug_for_user(person)
self._set_visibility(bug)
self.assertCommentHidden()
+
+ def test_pillar_owner_can_set_visible(self):
+ # Pillar owner can set bug comment visibility.
+ person = self.factory.makePerson()
+ naked_bugtask = removeSecurityProxy(self.bug.default_bugtask)
+ removeSecurityProxy(naked_bugtask.pillar).owner = person
+ bug = self._get_bug_for_user(person)
+ self._set_visibility(bug)
+ self.assertCommentHidden()
+
+ def test_pillar_driver_can_set_visible(self):
+ # Pillar driver can set bug comment visibility.
+ person = self.factory.makePerson()
+ naked_bugtask = removeSecurityProxy(self.bug.default_bugtask)
+ removeSecurityProxy(naked_bugtask.pillar).driver = person
+ bug = self._get_bug_for_user(person)
+ self._set_visibility(bug)
+ self.assertCommentHidden()
+
+ def test_pillar_bug_supervisor_can_set_visible(self):
+ # Pillar bug supervisor can set bug comment visibility.
+ person = self.factory.makePerson()
+ naked_bugtask = removeSecurityProxy(self.bug.default_bugtask)
+ removeSecurityProxy(naked_bugtask.pillar).bug_supervisor = person
+ bug = self._get_bug_for_user(person)
+ self._set_visibility(bug)
+ self.assertCommentHidden()
+
+ def test_pillar_security_contact_can_set_visible(self):
+ # Pillar security_contact can set bug comment visibility.
+ person = self.factory.makePerson()
+ naked_bugtask = removeSecurityProxy(self.bug.default_bugtask)
+ removeSecurityProxy(naked_bugtask.pillar).security_contact = person
+ bug = self._get_bug_for_user(person)
+ self._set_visibility(bug)
+ self.assertCommentHidden()
+
+ def test_comment_owner_can_set_visible(self):
+ # The author of the comment can set bug comment visibility.
+ person = self.factory.makePerson()
+ removeSecurityProxy(self.message).owner = person
+ bug = self._get_bug_for_user(person)
+ self._set_visibility(bug)
+ self.assertCommentHidden()
=== modified file 'lib/lp/coop/answersbugs/visibility.py'
--- lib/lp/coop/answersbugs/visibility.py 2011-05-17 13:36:44 +0000
+++ lib/lp/coop/answersbugs/visibility.py 2011-11-16 07:32:26 +0000
@@ -61,7 +61,7 @@
control_text = 'mark-spam-1'
- def getContext(self):
+ def getContext(self, comment_owner=None):
"""To be overwridden by subclasses.
This method must create and return a message bearing object
=== modified file 'lib/lp/registry/interfaces/role.py'
--- lib/lp/registry/interfaces/role.py 2011-11-10 01:13:44 +0000
+++ lib/lp/registry/interfaces/role.py 2011-11-16 07:32:26 +0000
@@ -128,6 +128,12 @@
def isDriver(obj):
"""Is this person the driver of the object?"""
+ def isBugSupervisor(obj):
+ """Is this person the bug supervisor of the object?"""
+
+ def isSecurityContact(obj):
+ """Is this person the security contact of the object?"""
+
def isOneOfDrivers(obj):
"""Is this person on of the drivers of the object?
=== modified file 'lib/lp/registry/model/personroles.py'
--- lib/lp/registry/model/personroles.py 2011-11-10 01:13:44 +0000
+++ lib/lp/registry/model/personroles.py 2011-11-16 07:32:26 +0000
@@ -13,6 +13,8 @@
from zope.interface import implements
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
+from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
+from lp.bugs.interfaces.securitycontact import IHasSecurityContact
from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.role import (
IHasDrivers,
@@ -49,6 +51,16 @@
"""See IPersonRoles."""
return self.person.inTeam(obj.owner)
+ def isBugSupervisor(self, obj):
+ """See IPersonRoles."""
+ return (IHasBugSupervisor.providedBy(obj)
+ and self.person.inTeam(obj.bug_supervisor))
+
+ def isSecurityContact(self, obj):
+ """See IPersonRoles."""
+ return (IHasSecurityContact.providedBy(obj)
+ and self.person.inTeam(obj.security_contact))
+
def isDriver(self, obj):
"""See IPersonRoles."""
return self.person.inTeam(obj.driver)
=== modified file 'lib/lp/registry/tests/test_personroles.py'
--- lib/lp/registry/tests/test_personroles.py 2011-11-10 01:13:44 +0000
+++ lib/lp/registry/tests/test_personroles.py 2011-11-16 07:32:26 +0000
@@ -115,6 +115,18 @@
roles = IPersonRoles(self.person)
self.assertFalse(roles.isOneOfDrivers(sprint))
+ def test_isBugSupervisor(self):
+ # The person can be the bug supervisor of something, e.g. a product.
+ product = self.factory.makeProduct(bug_supervisor=self.person)
+ roles = IPersonRoles(self.person)
+ self.assertTrue(roles.isBugSupervisor(product))
+
+ def test_isSecurityContact(self):
+ # The person can be the security contact of something, e.g. a product.
+ product = self.factory.makeProduct(security_contact=self.person)
+ roles = IPersonRoles(self.person)
+ self.assertTrue(roles.isSecurityContact(product))
+
def test_isOneOf(self):
# Objects may have multiple roles that a person can fulfill.
# Specifications are such a case.
=== modified file 'lib/lp/services/messages/configure.zcml'
--- lib/lp/services/messages/configure.zcml 2011-05-13 16:08:03 +0000
+++ lib/lp/services/messages/configure.zcml 2011-11-16 07:32:26 +0000
@@ -13,7 +13,7 @@
class="lp.services.messages.model.message.Message">
<allow interface="lp.services.messages.interfaces.message.IMessage" />
<require
- permission="launchpad.Admin"
+ permission="launchpad.AnyPerson"
set_attributes="visible"/>
</class>