← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cprov/launchpad/snap-privacy-take2 into lp:launchpad

 

Celso Providelo has proposed merging lp:~cprov/launchpad/snap-privacy-take2 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cprov/launchpad/snap-privacy-take2/+merge/285147

Preventing private snaps to be create via new feature-flag, so we can have more time to evaluate implications of underlying permission changes on branches.

Updating browser code to prevent +add-snap on private content according to the new feature-flag.

Also re-fixing ViewSnap to restrict access only to owners of the snap, not users who can view the owner (sounds trickier than it is, in fact).

-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cprov/launchpad/snap-privacy-take2 into lp:launchpad.
=== modified file 'lib/lp/security.py'
--- lib/lp/security.py	2016-02-04 00:45:12 +0000
+++ lib/lp/security.py	2016-02-05 14:47:09 +0000
@@ -3121,9 +3121,13 @@
         return not self.obj.private
 
     def checkAuthenticated(self, user):
+        if not self.obj.private:
+            return True
+
         return (
-            not self.obj.private or
-            self.forwardCheckAuthenticated(user, self.obj.owner))
+            user.isOwner(self.obj) or
+            user.in_commercial_admin or
+            user.in_admin)
 
 
 class EditSnap(AuthorizationBase):

=== modified file 'lib/lp/snappy/browser/hassnaps.py'
--- lib/lp/snappy/browser/hassnaps.py	2015-10-05 13:36:06 +0000
+++ lib/lp/snappy/browser/hassnaps.py	2016-02-05 14:47:09 +0000
@@ -22,6 +22,7 @@
 from lp.snappy.interfaces.snap import (
     ISnapSet,
     SNAP_FEATURE_FLAG,
+    SNAP_PRIVATE_FEATURE_FLAG,
     )
 
 
@@ -38,10 +39,16 @@
         return Link('+snaps', text, icon='info', enabled=enabled)
 
     def create_snap(self):
-        # You can't yet create a snap for a private branch.
-        enabled = (
-            bool(getFeatureFlag(SNAP_FEATURE_FLAG)) and
-            not self.context.private)
+        # Only enabled if the general snap feature flag is enabled
+        # for public contexts and additionally if the snap_private
+        # flag is enabled for private contexts.
+        if not bool(getFeatureFlag(SNAP_FEATURE_FLAG)):
+            enabled = False
+        else:
+            enabled = (
+                not self.context.private or
+                bool(getFeatureFlag(SNAP_PRIVATE_FEATURE_FLAG)))
+
         text = 'Create snap package'
         return Link('+new-snap', text, enabled=enabled, icon='add')
 

=== modified file 'lib/lp/snappy/browser/snap.py'
--- lib/lp/snappy/browser/snap.py	2016-02-04 00:59:43 +0000
+++ lib/lp/snappy/browser/snap.py	2016-02-05 14:47:09 +0000
@@ -36,6 +36,8 @@
     )
 from lp.app.browser.lazrjs import InlinePersonEditPickerWidget
 from lp.app.browser.tales import format_link
+from lp.app.enums import PRIVATE_INFORMATION_TYPES
+from lp.app.interfaces.informationtype import IInformationType
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.app.widgets.itemswidgets import (
     LabeledMultiCheckBoxWidget,
@@ -69,8 +71,10 @@
     ISnapSet,
     NoSuchSnap,
     SNAP_FEATURE_FLAG,
+    SNAP_PRIVATE_FEATURE_FLAG,
     SnapBuildAlreadyPending,
     SnapFeatureDisabled,
+    SnapPrivateFeatureDisabled,
     )
 from lp.snappy.interfaces.snapbuild import ISnapBuildSet
 from lp.soyuz.browser.archive import EnableProcessorsMixin
@@ -299,8 +303,16 @@
         """See `LaunchpadView`."""
         if not getFeatureFlag(SNAP_FEATURE_FLAG):
             raise SnapFeatureDisabled
+
         super(SnapAddView, self).initialize()
 
+        # Once initialized, if the private_snap flag is disabled, it
+        # prevents snap creation for private contexts.
+        if not getFeatureFlag(SNAP_PRIVATE_FEATURE_FLAG):
+            if (IInformationType.providedBy(self.context) and
+                self.context.information_type in PRIVATE_INFORMATION_TYPES):
+                raise SnapPrivateFeatureDisabled
+
     @property
     def cancel_url(self):
         return canonical_url(self.context)

=== modified file 'lib/lp/snappy/browser/tests/test_snap.py'
--- lib/lp/snappy/browser/tests/test_snap.py	2016-02-04 13:00:14 +0000
+++ lib/lp/snappy/browser/tests/test_snap.py	2016-02-05 14:47:09 +0000
@@ -24,6 +24,7 @@
 from zope.publisher.interfaces import NotFound
 from zope.security.interfaces import Unauthorized
 
+from lp.app.enums import InformationType
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.buildmaster.enums import BuildStatus
 from lp.buildmaster.interfaces.processor import IProcessorSet
@@ -42,13 +43,17 @@
 from lp.snappy.interfaces.snap import (
     CannotModifySnapProcessor,
     SNAP_FEATURE_FLAG,
+    SNAP_TESTING_FLAGS,
     SnapFeatureDisabled,
+    SnapPrivateFeatureDisabled,
     )
 from lp.testing import (
     BrowserTestCase,
+    feature_flags,
     login,
     login_person,
     person_logged_in,
+    set_feature_flag,
     TestCaseWithFactory,
     time_counter,
     )
@@ -79,7 +84,7 @@
 
     def setUp(self):
         super(TestSnapNavigation, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
 
     def test_canonical_url(self):
         owner = self.factory.makePerson(name="person")
@@ -105,6 +110,19 @@
         self.assertRaises(
             SnapFeatureDisabled, create_initialized_view, branch, "+new-snap")
 
+    def test_private_feature_flag_disabled(self):
+        # Without a private_snap feature flag, we will not create Snaps for
+        # private contexts.
+        owner = self.factory.makePerson()
+        branch = self.factory.makeAnyBranch(
+            owner=owner, information_type=InformationType.USERDATA)
+        with feature_flags():
+            set_feature_flag(SNAP_FEATURE_FLAG, u'on')
+            with person_logged_in(owner):
+                self.assertRaises(
+                    SnapPrivateFeatureDisabled, create_initialized_view,
+                    branch, "+new-snap")
+
 
 class TestSnapAddView(BrowserTestCase):
 
@@ -112,7 +130,7 @@
 
     def setUp(self):
         super(TestSnapAddView, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.useFixture(FakeLogger())
         self.person = self.factory.makePerson(
             name="test-person", displayname="Test Person")
@@ -215,6 +233,22 @@
             extract_text(find_tag_by_id(browser.contents, "privacy"))
         )
 
+    def test_create_new_snap_private_link(self):
+        # Link for create new snaps for private content is only displayed
+        # if the 'snap.allow_private' is enabled.
+        login_person(self.person)
+        branch = self.factory.makeAnyBranch(
+            owner=self.person,
+            information_type=InformationType.USERDATA)
+
+        browser = self.getViewBrowser(branch, user=self.person)
+        browser.getLink('Create snap package')
+
+        with FeatureFixture({SNAP_FEATURE_FLAG: u'on'}):
+            browser = self.getViewBrowser(branch, user=self.person)
+            self.assertRaises(
+                LinkNotFoundError, browser.getLink, "Create snap package")
+
     def test_create_new_snap_private(self):
         # Private teams will automatically create private snaps.
         login_person(self.person)
@@ -243,7 +277,7 @@
 
     def setUp(self):
         super(TestSnapAdminView, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.useFixture(FakeLogger())
         self.person = self.factory.makePerson(
             name="test-person", displayname="Test Person")
@@ -263,14 +297,14 @@
     def test_admin_snap(self):
         # Admins can change require_virtualized and privacy.
         login("admin@xxxxxxxxxxxxx")
-        ppa_admin = self.factory.makePerson(
-            member_of=[getUtility(ILaunchpadCelebrities).ppa_admin])
+        commercial_admin = self.factory.makePerson(
+            member_of=[getUtility(ILaunchpadCelebrities).commercial_admin])
         login_person(self.person)
         snap = self.factory.makeSnap(registrant=self.person)
         self.assertTrue(snap.require_virtualized)
         self.assertFalse(snap.private)
 
-        browser = self.getViewBrowser(snap, user=ppa_admin)
+        browser = self.getViewBrowser(snap, user=commercial_admin)
         browser.getLink("Administer snap package").click()
         browser.getControl("Require virtualized builders").selected = False
         browser.getControl("Private").selected = True
@@ -321,7 +355,7 @@
 
     def setUp(self):
         super(TestSnapEditView, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.useFixture(FakeLogger())
         self.person = self.factory.makePerson(
             name="test-person", displayname="Test Person")
@@ -531,7 +565,7 @@
 
     def setUp(self):
         super(TestSnapDeleteView, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.person = self.factory.makePerson(
             name="test-person", displayname="Test Person")
 
@@ -577,7 +611,7 @@
 
     def setUp(self):
         super(TestSnapView, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
         self.distroseries = self.factory.makeDistroSeries(
             distribution=self.ubuntu, name="shiny", displayname="Shiny")
@@ -748,7 +782,7 @@
 
     def setUp(self):
         super(TestSnapRequestBuildsView, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.useFixture(FakeLogger())
         self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
         self.distroseries = self.factory.makeDistroSeries(

=== modified file 'lib/lp/snappy/interfaces/snap.py'
--- lib/lp/snappy/interfaces/snap.py	2016-02-04 00:45:12 +0000
+++ lib/lp/snappy/interfaces/snap.py	2016-02-05 14:47:09 +0000
@@ -16,12 +16,15 @@
     'NoSourceForSnap',
     'NoSuchSnap',
     'SNAP_FEATURE_FLAG',
+    'SNAP_PRIVATE_FEATURE_FLAG',
+    'SNAP_TESTING_FLAGS',
     'SnapBuildAlreadyPending',
     'SnapBuildArchiveOwnerMismatch',
     'SnapBuildDisallowedArchitecture',
     'SnapFeatureDisabled',
     'SnapNotOwner',
     'SnapPrivacyMismatch',
+    'SnapPrivateFeatureDisabled',
     ]
 
 import httplib
@@ -87,6 +90,13 @@
 
 
 SNAP_FEATURE_FLAG = u"snap.allow_new"
+SNAP_PRIVATE_FEATURE_FLAG = u"snap.allow_private"
+
+
+SNAP_TESTING_FLAGS = {
+    SNAP_FEATURE_FLAG: u"on",
+    SNAP_PRIVATE_FEATURE_FLAG: u"on",
+}
 
 
 @error_status(httplib.BAD_REQUEST)
@@ -136,6 +146,15 @@
             "builds.")
 
 
+@error_status(httplib.UNAUTHORIZED)
+class SnapPrivateFeatureDisabled(Unauthorized):
+    """Only certain users can create private snap objects."""
+
+    def __init__(self):
+        super(SnapPrivateFeatureDisabled, self).__init__(
+            "You do not have permission to create private snaps")
+
+
 @error_status(httplib.BAD_REQUEST)
 class DuplicateSnapName(Exception):
     """Raised for snap packages with duplicate name/owner."""

=== modified file 'lib/lp/snappy/model/snap.py'
--- lib/lp/snappy/model/snap.py	2016-02-04 13:00:14 +0000
+++ lib/lp/snappy/model/snap.py	2016-02-05 14:47:09 +0000
@@ -81,12 +81,14 @@
     NoSourceForSnap,
     NoSuchSnap,
     SNAP_FEATURE_FLAG,
+    SNAP_PRIVATE_FEATURE_FLAG,
     SnapBuildAlreadyPending,
     SnapBuildArchiveOwnerMismatch,
     SnapBuildDisallowedArchitecture,
     SnapFeatureDisabled,
     SnapNotOwner,
     SnapPrivacyMismatch,
+    SnapPrivateFeatureDisabled,
     )
 from lp.snappy.interfaces.snapbuild import ISnapBuildSet
 from lp.snappy.model.snapbuild import SnapBuild
@@ -411,8 +413,11 @@
 
     def isValidPrivacy(self, private, owner, branch=None, git_ref=None):
         """See `ISnapSet`."""
-        # Private snaps may contain anything.
+        # Private snaps may contain anything ...
         if private:
+            # If appropriately enabled via feature flag.
+            if not getFeatureFlag(SNAP_PRIVATE_FEATURE_FLAG):
+                raise SnapPrivateFeatureDisabled
             return True
 
         # Public snaps with private sources are not allowed.

=== modified file 'lib/lp/snappy/tests/test_snap.py'
--- lib/lp/snappy/tests/test_snap.py	2016-02-04 00:45:12 +0000
+++ lib/lp/snappy/tests/test_snap.py	2016-02-05 14:47:09 +0000
@@ -42,19 +42,23 @@
     ISnapView,
     NoSourceForSnap,
     SNAP_FEATURE_FLAG,
+    SNAP_TESTING_FLAGS,
     SnapBuildAlreadyPending,
     SnapBuildDisallowedArchitecture,
     SnapFeatureDisabled,
     SnapPrivacyMismatch,
+    SnapPrivateFeatureDisabled,
     )
 from lp.snappy.interfaces.snapbuild import ISnapBuild
 from lp.testing import (
     admin_logged_in,
     ANONYMOUS,
     api_url,
+    feature_flags,
     login,
     logout,
     person_logged_in,
+    set_feature_flag,
     StormStatementRecorder,
     TestCaseWithFactory,
     )
@@ -81,6 +85,16 @@
             SnapFeatureDisabled, getUtility(ISnapSet).new,
             person, person, None, None, branch=self.factory.makeAnyBranch())
 
+    def test_private_feature_flag_disabled(self):
+        # Without a private feature flag, we will not create new private Snaps.
+        person = self.factory.makePerson()
+        with feature_flags():
+            set_feature_flag(SNAP_FEATURE_FLAG, u'on')
+            self.assertRaises(
+                SnapPrivateFeatureDisabled, getUtility(ISnapSet).new,
+                person, person, None, None,
+                branch=self.factory.makeAnyBranch(), private=True)
+
 
 class TestSnap(TestCaseWithFactory):
 
@@ -88,7 +102,7 @@
 
     def setUp(self):
         super(TestSnap, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
 
     def test_implements_interfaces(self):
         # Snap implements ISnap.
@@ -364,7 +378,7 @@
 
     def setUp(self):
         super(TestSnapSet, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
 
     def test_class_implements_interfaces(self):
         # The SnapSet class implements ISnapSet.
@@ -668,7 +682,7 @@
 
     def setUp(self):
         super(TestSnapProcessors, self).setUp(user="foo.bar@xxxxxxxxxxxxx")
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.default_procs = [
             getUtility(IProcessorSet).getByName("386"),
             getUtility(IProcessorSet).getByName("amd64")]
@@ -755,7 +769,7 @@
 
     def setUp(self):
         super(TestSnapWebservice, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.person = self.factory.makePerson(displayname="Test Person")
         self.webservice = webservice_for_person(
             self.person, permission=OAuthPermission.WRITE_PUBLIC)

=== modified file 'lib/lp/snappy/tests/test_snapbuild.py'
--- lib/lp/snappy/tests/test_snapbuild.py	2016-02-04 00:45:12 +0000
+++ lib/lp/snappy/tests/test_snapbuild.py	2016-02-05 14:47:09 +0000
@@ -30,7 +30,7 @@
 from lp.services.librarian.browser import ProxiedLibraryFileAlias
 from lp.services.webapp.interfaces import OAuthPermission
 from lp.snappy.interfaces.snap import (
-    SNAP_FEATURE_FLAG,
+    SNAP_TESTING_FLAGS,
     SnapFeatureDisabled,
     )
 from lp.snappy.interfaces.snapbuild import (
@@ -89,7 +89,7 @@
 
     def setUp(self):
         super(TestSnapBuild, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.build = self.factory.makeSnapBuild()
 
     def test_implements_interfaces(self):
@@ -127,7 +127,8 @@
             visibility=PersonVisibility.PRIVATE)
         with person_logged_in(private_team.teamowner):
             build = self.factory.makeSnapBuild(
-                requester=private_team.teamowner, owner=private_team, private=True)
+                requester=private_team.teamowner, owner=private_team,
+                private=True)
             self.assertTrue(build.is_private)
         private_archive = self.factory.makeArchive(private=True)
         with person_logged_in(private_archive.owner):
@@ -287,7 +288,7 @@
 
     def setUp(self):
         super(TestSnapBuildSet, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
 
     def test_getByBuildFarmJob_works(self):
         build = self.factory.makeSnapBuild()
@@ -318,7 +319,7 @@
 
     def setUp(self):
         super(TestSnapBuildWebservice, self).setUp()
-        self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
+        self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
         self.person = self.factory.makePerson()
         self.webservice = webservice_for_person(
             self.person, permission=OAuthPermission.WRITE_PRIVATE)


Follow ups