← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ines-almeida/launchpad:update-bugpresence-permissions into launchpad:master

 

Ines Almeida has proposed merging ~ines-almeida/launchpad:update-bugpresence-permissions into launchpad:master.

Commit message:
Fix BugPresence permissions to grant view access to certain roles

This is a relatively new model that isn't used widely, but will be used by the UCT exporter

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/493612
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ines-almeida/launchpad:update-bugpresence-permissions into launchpad:master.
diff --git a/lib/lp/bugs/configure.zcml b/lib/lp/bugs/configure.zcml
index 0c6c864..25f2d61 100644
--- a/lib/lp/bugs/configure.zcml
+++ b/lib/lp/bugs/configure.zcml
@@ -947,7 +947,6 @@
                 permission="launchpad.View"
                 set_schema="lp.bugs.interfaces.bugpresence.IBugPresence"
                 attributes="
-                    id
                     bug
                     product
                     distribution
@@ -955,7 +954,7 @@
                     git_repository
                     break_fix_data"/>
             <require
-                permission="launchpad.Edit"
+                permission="launchpad.Delete"
                 attributes="destroySelf"/>
         </class>
 
diff --git a/lib/lp/bugs/interfaces/bugpresence.py b/lib/lp/bugs/interfaces/bugpresence.py
index 3eef378..b072a61 100644
--- a/lib/lp/bugs/interfaces/bugpresence.py
+++ b/lib/lp/bugs/interfaces/bugpresence.py
@@ -7,7 +7,7 @@ __all__ = [
     "IBugPresence",
     "IBugPresenceSet",
 ]
-
+from lazr.restful.declarations import exported
 from zope.interface import Interface
 from zope.schema import Dict, Int
 
@@ -19,12 +19,14 @@ class IBugPresence(Interface):
     """A single `BugPresence` database entry."""
 
     id = Int(title=_("ID"), required=True, readonly=True)
-    bug = BugField(title=_("Bug"), readonly=True)
-    product = Int(title=_("Product"))
-    distribution = Int(title=_("Distribution"))
-    source_package_name = Int(title=_("Source Package Name"))
-    git_repository = Int(title=_("Git Repository"))
-    break_fix_data = Dict(title=_("Break-Fix"))
+    bug = exported(BugField(title=_("Bug"), readonly=True))
+    product = exported(Int(title=_("Product"), readonly=True))
+    distribution = exported(Int(title=_("Distribution"), readonly=True))
+    source_package_name = exported(
+        Int(title=_("Source Package Name"), readonly=True)
+    )
+    git_repository = exported(Int(title=_("Git Repository"), readonly=True))
+    break_fix_data = exported(Dict(title=_("Break-Fix"), readonly=True))
 
     def destroySelf(self):
         """Destroy this `IBugPresence` object."""
diff --git a/lib/lp/bugs/model/tests/test_bugpresence.py b/lib/lp/bugs/model/tests/test_bugpresence.py
new file mode 100644
index 0000000..fd311ad
--- /dev/null
+++ b/lib/lp/bugs/model/tests/test_bugpresence.py
@@ -0,0 +1,70 @@
+# Copyright 2025 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for the bugpresence model."""
+
+from zope.component import getUtility
+from zope.security.management import checkPermission
+
+from lp.bugs.interfaces.bugpresence import IBugPresenceSet
+from lp.testing import (
+    TestCaseWithFactory,
+    admin_logged_in,
+    celebrity_logged_in,
+    person_logged_in,
+)
+from lp.testing.layers import DatabaseFunctionalLayer
+
+
+class TestBugPresence(TestCaseWithFactory):
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super().setUp()
+        self.bug = self.factory.makeBug()
+        self.bugpresence = getUtility(IBugPresenceSet).create(
+            bug=self.bug,
+            product=None,
+            distribution=None,
+            source_package_name=None,
+            git_repository=None,
+            break_fix_data=[],
+        )
+
+    def test_view_denied_to_anonymous(self):
+        self.assertFalse(checkPermission("launchpad.View", self.bugpresence))
+
+    def test_view_allowed_to_admin(self):
+        with admin_logged_in():
+            self.assertTrue(
+                checkPermission("launchpad.View", self.bugpresence)
+            )
+
+    def test_view_allowed_to_registry_experts(self):
+        with celebrity_logged_in("registry_experts"):
+            self.assertTrue(
+                checkPermission("launchpad.View", self.bugpresence)
+            )
+
+    def test_view_allowed_to_owner_role(self):
+        owner = self.bug.default_bugtask.target.owner
+        with person_logged_in(owner):
+            self.assertTrue(
+                checkPermission("launchpad.View", self.bugpresence)
+            )
+
+    def test_delete_denied_to_anonymous(self):
+        self.assertFalse(checkPermission("launchpad.Delete", self.bugpresence))
+
+    def test_delete_allowed_to_admin(self):
+        with admin_logged_in():
+            self.assertTrue(
+                checkPermission("launchpad.Delete", self.bugpresence)
+            )
+
+    def test_delete_denied_to_owner_role(self):
+        owner = self.bug.default_bugtask.target.owner
+        with person_logged_in(owner):
+            self.assertFalse(
+                checkPermission("launchpad.Delete", self.bugpresence)
+            )
diff --git a/lib/lp/bugs/security.py b/lib/lp/bugs/security.py
index 4d961ac..76fb30a 100644
--- a/lib/lp/bugs/security.py
+++ b/lib/lp/bugs/security.py
@@ -17,6 +17,7 @@ from lp.bugs.interfaces.bug import IBug
 from lp.bugs.interfaces.bugactivity import IBugActivity
 from lp.bugs.interfaces.bugattachment import IBugAttachment
 from lp.bugs.interfaces.bugnomination import IBugNomination
+from lp.bugs.interfaces.bugpresence import IBugPresence
 from lp.bugs.interfaces.bugsubscription import IBugSubscription
 from lp.bugs.interfaces.bugsubscriptionfilter import IBugSubscriptionFilter
 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
@@ -309,6 +310,37 @@ class EditBugAttachment(AuthorizationBase):
         return False
 
 
+class ViewBugPresence(DelegatedAuthorization):
+    """Security adapter for viewing a bug presence.
+
+    If the user is authorized to view the bug, they're allowed to view the
+    bug presence.
+    """
+
+    def checkAuthenticated(self, user):
+        return (
+            user.in_admin
+            or user.in_registry_experts
+            or _has_any_bug_role(user, self.obj.bug.bugtasks)
+        )
+
+    def checkUnauthenticated(self):
+        return False
+
+
+class DeleteBugPresence(DelegatedAuthorization):
+    """Security adapter for deleting a bug presence."""
+
+    permission = "launchpad.Delete"
+    usedfor = IBugPresence
+
+    def checkAuthenticated(self, user):
+        return user.in_admin
+
+    def checkUnauthenticated(self):
+        return False
+
+
 class ViewBugActivity(DelegatedAuthorization):
     """Security adapter for viewing a bug activity record.