← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:reorganize-distribution-permissions into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:reorganize-distribution-permissions into launchpad:master with ~cjwatson/launchpad:commercial-subscription-distribution as a prerequisite.

Commit message:
Introduce View and LimitedView permissions on distributions

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/415314

For now these are trivially public, so no functional change is expected here.  That will change as soon as we put distribution privacy in place.

As with projects, only the bare minimum of attributes remain public; attributes needed by people who can see some individual artifacts of the distribution (such as those needed to render links to it) are under `LimitedView`; and everything else that was formerly public is now under `View`.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:reorganize-distribution-permissions into launchpad:master.
diff --git a/lib/lp/registry/browser/tests/test_ociproject.py b/lib/lp/registry/browser/tests/test_ociproject.py
index 4837f5b..229b30c 100644
--- a/lib/lp/registry/browser/tests/test_ociproject.py
+++ b/lib/lp/registry/browser/tests/test_ociproject.py
@@ -340,7 +340,7 @@ class TestOCIProjectView(OCIConfigHelperMixin, BrowserTestCase):
             official=True, information_type=InformationType.PRIVATESECURITY)
 
         browser = self.getViewBrowser(
-            oci_project, view_name="+index", user=distribution.owner)
+            oci_project, view_name="+index", user=self.factory.makePerson())
         self.assertNotIn("Official recipes", browser.contents)
         self.assertNotIn("unofficial recipe", browser.contents)
         self.assertIn(
diff --git a/lib/lp/registry/configure.zcml b/lib/lp/registry/configure.zcml
index 7edbccb..fbeaffd 100644
--- a/lib/lp/registry/configure.zcml
+++ b/lib/lp/registry/configure.zcml
@@ -1820,8 +1820,14 @@
     <class
         class="lp.registry.model.distribution.Distribution">
         <allow
-            interface="lp.registry.interfaces.distribution.IDistributionPublic"/>
-        <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
+            interface="lp.registry.interfaces.distribution.IDistributionPublic
+                       lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
+        <require
+            permission="launchpad.LimitedView"
+            interface="lp.registry.interfaces.distribution.IDistributionLimitedView"/>
+        <require
+            permission="launchpad.View"
+            interface="lp.registry.interfaces.distribution.IDistributionView"/>
         <require
             permission="launchpad.Edit"
             interface="lp.registry.interfaces.distribution.IDistributionEditRestricted"/>
diff --git a/lib/lp/registry/interfaces/distribution.py b/lib/lp/registry/interfaces/distribution.py
index 6c9b00d..b102de8 100644
--- a/lib/lp/registry/interfaces/distribution.py
+++ b/lib/lp/registry/interfaces/distribution.py
@@ -60,6 +60,9 @@ from lp.answers.interfaces.faqtarget import IFAQTarget
 from lp.answers.interfaces.questiontarget import IQuestionTarget
 from lp.app.errors import NameLookupFailed
 from lp.app.interfaces.launchpad import (
+    IHasIcon,
+    IHasLogo,
+    IHasMugshot,
     ILaunchpadUsage,
     IServiceUsage,
     )
@@ -141,39 +144,6 @@ class DistributionNameField(PillarNameField):
         return IDistribution
 
 
-class IDistributionEditRestricted(IOfficialBugTagTargetRestricted):
-    """IDistribution properties requiring launchpad.Edit permission."""
-
-    @call_with(registrant=REQUEST_USER)
-    @operation_parameters(
-        registry_url=TextLine(
-            title=_("The registry url."),
-            description=_("The url of the OCI registry to use."),
-            required=True),
-        region=TextLine(
-            title=_("OCI registry region."),
-            description=_("The region of the OCI registry."),
-            required=False),
-        username=TextLine(
-            title=_("Username"),
-            description=_("The username for the OCI registry."),
-            required=False),
-        password=TextLine(
-            title=_("Password"),
-            description=_("The password for the OCI registry."),
-            required=False))
-    @export_write_operation()
-    @operation_for_version("devel")
-    def setOCICredentials(registrant, registry_url, region,
-                          username, password):
-        """Set the credentials for the OCI registry for OCI projects."""
-
-    @export_write_operation()
-    @operation_for_version("devel")
-    def deleteOCICredentials():
-        """Delete any existing OCI credentials for the distribution."""
-
-
 class IDistributionDriverRestricted(Interface):
     """IDistribution properties requiring launchpad.Driver permission."""
 
@@ -182,16 +152,21 @@ class IDistributionDriverRestricted(Interface):
         """Creates a new distroseries."""
 
 
-class IDistributionPublic(
-    IBugTarget, ICanGetMilestonesDirectly, IHasAppointedDriver,
-    IHasBuildRecords, IHasDrivers, IHasMilestones, IHasSharingPolicies,
-    IHasOOPSReferences, IHasOwner, IHasSprints, IHasTranslationImports,
-    ITranslationPolicy, IKarmaContext, ILaunchpadUsage, IMakesAnnouncements,
-    IOfficialBugTagTargetPublic, IPillar, IServiceUsage,
-    ISpecificationTarget, IHasExpirableBugs):
+class IDistributionPublic(Interface):
     """Public IDistribution properties."""
 
     id = Attribute("The distro's unique number.")
+
+    def userCanView(user):
+        """True if the given user has view access to this distribution."""
+
+    def userCanLimitedView(user):
+        """True if the given user has limited access to this distribution."""
+
+
+class IDistributionLimitedView(IHasIcon, IHasLogo, IHasOwner, ILaunchpadUsage):
+    """IDistribution attributes visible to people with artifact grants."""
+
     name = exported(
         DistributionNameField(
             title=_("Name"),
@@ -207,20 +182,6 @@ class IDistributionPublic(
         Title(
             title=_("Title"),
             description=_("The distro's title."), required=True))
-    summary = exported(
-        Summary(
-            title=_("Summary"),
-            description=_(
-                "A short paragraph to introduce the goals and highlights "
-                "of the distribution."),
-            required=True))
-    homepage_content = exported(
-        Text(
-            title=_("Homepage Content"), required=False,
-            description=_(
-                "The content of this distribution's home page. Edit this and "
-                "it will be displayed for all the world to see. It is NOT a "
-                "wiki so you cannot undo changes.")))
     icon = exported(
         IconImageUpload(
             title=_("Icon"), required=False,
@@ -238,6 +199,43 @@ class IDistributionPublic(
                 "An image of exactly 64x64 pixels that will be displayed in "
                 "the heading of all pages related to this distribution. It "
                 "should be no bigger than 50kb in size.")))
+    owner = exported(
+        PublicPersonChoice(
+            title=_("Owner"),
+            required=True,
+            vocabulary='ValidPillarOwner',
+            description=_("The restricted team, moderated team, or person "
+                          "who maintains the distribution information in "
+                          "Launchpad.")))
+
+    @operation_parameters(
+        name=TextLine(title=_("OCI project name"), required=True))
+    # Really returns IOCIProject, see _schema_circular_imports.py.
+    @operation_returns_entry(Interface)
+    @export_read_operation()
+    @operation_for_version("devel")
+    def getOCIProject(name):
+        """Return a `OCIProject` with the given name for this
+        distribution, or None.
+        """
+
+
+class IDistributionView(
+        IHasMugshot, IBugTarget, ICanGetMilestonesDirectly,
+        IHasAppointedDriver, IHasBuildRecords, IHasDrivers, IHasMilestones,
+        IHasSharingPolicies, IHasOOPSReferences, IHasSprints,
+        IHasTranslationImports, ITranslationPolicy, IKarmaContext,
+        IMakesAnnouncements, IOfficialBugTagTargetPublic, IPillar,
+        IServiceUsage, ISpecificationTarget, IHasExpirableBugs):
+    """IDistribution attributes requiring launchpad.View."""
+
+    homepage_content = exported(
+        Text(
+            title=_("Homepage Content"), required=False,
+            description=_(
+                "The content of this distribution's home page. Edit this and "
+                "it will be displayed for all the world to see. It is NOT a "
+                "wiki so you cannot undo changes.")))
     mugshot = exported(
         MugshotImageUpload(
             title=_("Brand"), required=False,
@@ -246,6 +244,13 @@ class IDistributionPublic(
                 "A large image of exactly 192x192 pixels, that will be "
                 "displayed on this distribution's home page in Launchpad. "
                 "It should be no bigger than 100kb in size. ")))
+    summary = exported(
+        Summary(
+            title=_("Summary"),
+            description=_(
+                "A short paragraph to introduce the goals and highlights "
+                "of the distribution."),
+            required=True))
     description = exported(
         Description(
             title=_("Description"),
@@ -260,14 +265,6 @@ class IDistributionPublic(
             title=_("Web site URL"),
             description=_("The distro's web site URL."), required=True),
         exported_as='domain_name')
-    owner = exported(
-        PublicPersonChoice(
-            title=_("Owner"),
-            required=True,
-            vocabulary='ValidPillarOwner',
-            description=_("The restricted team, moderated team, or person "
-                          "who maintains the distribution information in "
-                          "Launchpad.")))
     registrant = exported(
         PublicPersonChoice(
             title=_("Registrant"), vocabulary='ValidPersonOrTeam',
@@ -576,17 +573,6 @@ class IDistributionPublic(
         """
 
     @operation_parameters(
-        name=TextLine(title=_("OCI project name"), required=True))
-    # Really returns IOCIProject, see _schema_circular_imports.py.
-    @operation_returns_entry(Interface)
-    @export_read_operation()
-    @operation_for_version("devel")
-    def getOCIProject(name):
-        """Return a `OCIProject` with the given name for this
-        distribution, or None.
-        """
-
-    @operation_parameters(
         text=TextLine(title=_("OCI title substring match "), required=False))
     # Really returns IOCIProject, see
     # _schema_circular_imports.py.
@@ -778,10 +764,45 @@ class IDistributionPublic(
                       "images in this distribution to a registry."),
         required=False, readonly=False)
 
+
+class IDistributionEditRestricted(IOfficialBugTagTargetRestricted):
+    """IDistribution properties requiring launchpad.Edit permission."""
+
+    @call_with(registrant=REQUEST_USER)
+    @operation_parameters(
+        registry_url=TextLine(
+            title=_("The registry url."),
+            description=_("The url of the OCI registry to use."),
+            required=True),
+        region=TextLine(
+            title=_("OCI registry region."),
+            description=_("The region of the OCI registry."),
+            required=False),
+        username=TextLine(
+            title=_("Username"),
+            description=_("The username for the OCI registry."),
+            required=False),
+        password=TextLine(
+            title=_("Password"),
+            description=_("The password for the OCI registry."),
+            required=False))
+    @export_write_operation()
+    @operation_for_version("devel")
+    def setOCICredentials(registrant, registry_url, region,
+                          username, password):
+        """Set the credentials for the OCI registry for OCI projects."""
+
+    @export_write_operation()
+    @operation_for_version("devel")
+    def deleteOCICredentials():
+        """Delete any existing OCI credentials for the distribution."""
+
+
 @exported_as_webservice_entry(as_of="beta")
 class IDistribution(
-    IDistributionEditRestricted, IDistributionPublic, IHasBugSupervisor,
-    IFAQTarget, IQuestionTarget, IStructuralSubscriptionTarget):
+        IDistributionEditRestricted, IDistributionPublic,
+        IDistributionLimitedView, IDistributionView, IHasBugSupervisor,
+        IFAQTarget, IQuestionTarget, IStructuralSubscriptionTarget):
     """An operating system distribution.
 
     Launchpadlib example: retrieving the current version of a package in a
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index 867c4df..c6327d2 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -1568,6 +1568,18 @@ class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
             self.oci_registry_credentials = None
             old_credentials.destroySelf()
 
+    def userCanView(self, user):
+        """See `IDistributionPublic`."""
+        # All distributions are public until we finish introducing privacy
+        # support.
+        return True
+
+    def userCanLimitedView(self, user):
+        """See `IDistributionPublic`."""
+        # All distributions are public until we finish introducing privacy
+        # support.
+        return True
+
 
 @implementer(IDistributionSet)
 class DistributionSet:
diff --git a/lib/lp/security.py b/lib/lp/security.py
index d70a99b..22bc140 100644
--- a/lib/lp/security.py
+++ b/lib/lp/security.py
@@ -1244,6 +1244,27 @@ class EditPollOptionByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
         return can_edit_team(self.obj.poll.team, user)
 
 
+class ViewDistribution(AuthorizationBase):
+    permission = 'launchpad.View'
+    usedfor = IDistribution
+
+    def checkAuthenticated(self, user):
+        return self.obj.userCanView(user)
+
+    def checkUnauthenticated(self):
+        return self.obj.userCanView(None)
+
+
+class LimitedViewDistribution(ViewDistribution):
+    permission = 'launchpad.LimitedView'
+    usedfor = IDistribution
+
+    def checkAuthenticated(self, user):
+        return (
+            super().checkAuthenticated(user) or
+            self.obj.userCanLimitedView(user))
+
+
 class AdminDistribution(AdminByAdminsTeam):
     """Soyuz involves huge chunks of data in the archive and librarian,
     so for the moment we are locking down admin and edit on distributions