launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #26583
Re: [Merge] ~pappacena/launchpad:snap-pillar-subscribe into launchpad:master
Pushed the requested changes.
Diff comments:
> diff --git a/lib/lp/registry/tests/test_personmerge.py b/lib/lp/registry/tests/test_personmerge.py
> index 02fefcc..4c55cb2 100644
> --- a/lib/lp/registry/tests/test_personmerge.py
> +++ b/lib/lp/registry/tests/test_personmerge.py
> @@ -664,6 +669,35 @@ class TestMergePeople(TestCaseWithFactory, KarmaTestMixin):
> self.assertIsNone(snaps[1].git_path)
> self.assertEqual(u'foo-1', snaps[1].name)
>
> + def test_merge_snapsubscription(self):
> + # Checks that merging users moves subscriptions.
> + self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
> + duplicate = self.factory.makePerson()
> + mergee = self.factory.makePerson()
> + snap = removeSecurityProxy(self.factory.makeSnap(
> + owner=duplicate, registrant=duplicate,
> + name=u'foo', private=True))
> + self._do_premerge(duplicate, mergee)
> + login_admin()
It makes sense. Fixing it.
> + # Owner should have being subscribed automatically on creation.
> + self.assertTrue(snap.visibleByUser(duplicate))
> + self.assertThat(snap._getSubscription(duplicate), MatchesStructure(
> + snap=Equals(snap),
> + person=Equals(duplicate)
> + ))
> + self.assertFalse(snap.visibleByUser(mergee))
> + self.assertIsNone(snap._getSubscription(mergee))
> +
> + duplicate, mergee = self._do_merge(duplicate, mergee)
> +
> + self.assertTrue(snap.visibleByUser(mergee))
> + self.assertThat(snap._getSubscription(mergee), MatchesStructure(
> + snap=Equals(snap),
> + person=Equals(mergee)
> + ))
> + self.assertFalse(snap.visibleByUser(duplicate))
> + self.assertIsNone(snap._getSubscription(duplicate))
> +
> def test_merge_moves_oci_recipes(self):
> # When person/teams are merged, oci recipes owned by the from
> # person are moved.
> diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py
> index 6b9af0a..1b98ad5 100644
> --- a/lib/lp/snappy/model/snap.py
> +++ b/lib/lp/snappy/model/snap.py
> @@ -1129,25 +1137,67 @@ class Snap(Storm, WebhookTargetMixin):
>
> def visibleByUser(self, user):
> """See `ISnap`."""
> + if self.information_type in PUBLIC_INFORMATION_TYPES:
> + return True
> + if user is None:
> + return False
> store = IStore(self)
> return not store.find(
> Snap,
> Snap.id == self.id,
> get_snap_privacy_filter(user)).is_empty()
>
> + def _getSubscription(self, person):
> + """Returns person's subscription to this snap recipe, or None if no
> + subscription is available.
> + """
> + if person is None:
> + return None
> + return Store.of(self).find(
> + SnapSubscription,
> + SnapSubscription.person == person,
> + SnapSubscription.snap == self).one()
> +
> + def _userCanBeSubscribed(self, person):
> + """Checks if the given person can subscribe to this snap recipe."""
> + return not (
> + self.private and
> + person.is_team and
> + person.anyone_can_join())
> +
> def subscribe(self, person, subscribed_by, ignore_permissions=False):
> """See `ISnap`."""
> - # XXX pappacena 2021-02-05: We may need a "SnapSubscription" here.
> + if not self._userCanBeSubscribed(person):
> + raise SubscriptionPrivacyViolation(
> + "Open and delegated teams cannot be subscribed to private "
> + "snap recipes.")
> + subscription = self._getSubscription(person)
> + if subscription is None:
> + subscription = SnapSubscription(
> + person=person, snap=self, subscribed_by=subscribed_by)
> + Store.of(subscription).flush()
> service = getUtility(IService, "sharing")
> service.ensureAccessGrants(
> [person], subscribed_by, snaps=[self],
> ignore_permissions=ignore_permissions)
>
> - def unsubscribe(self, person, unsubscribed_by):
> + def unsubscribe(self, person, unsubscribed_by, ignore_permissions=False):
> """See `ISnap`."""
> - service = getUtility(IService, "sharing")
> - service.revokeAccessGrants(
> - self.pillar, person, unsubscribed_by, snaps=[self])
> + subscription = self._getSubscription(person)
Ok.
> + if (not ignore_permissions
> + and not subscription.canBeUnsubscribedByUser(unsubscribed_by)):
> + raise UserCannotUnsubscribePerson(
> + '%s does not have permission to unsubscribe %s.' % (
> + unsubscribed_by.displayname,
> + person.displayname))
> + artifact = getUtility(IAccessArtifactSource).find([self])
> + getUtility(IAccessArtifactGrantSource).revokeByArtifact(
> + artifact, [person])
> + # It should never be None, since we always create a SnapSubscription
> + # on Snap.subscribe. But just in case...
> + if subscription is not None:
> + store = Store.of(subscription)
> + store.remove(subscription)
> IStore(self).flush()
>
> def _reconcileAccess(self):
> diff --git a/lib/lp/snappy/model/snapsubscription.py b/lib/lp/snappy/model/snapsubscription.py
> new file mode 100644
> index 0000000..95a0026
> --- /dev/null
> +++ b/lib/lp/snappy/model/snapsubscription.py
> @@ -0,0 +1,61 @@
> +# Copyright 2020-2021 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +"""Snap subscription model."""
> +
> +from __future__ import absolute_import, print_function, unicode_literals
> +
> +__metaclass__ = type
> +__all__ = [
> + 'SnapSubscription'
> +]
> +
> +import pytz
> +from storm.properties import (
> + DateTime,
> + Int,
> + )
> +from storm.references import Reference
> +from zope.interface import implementer
> +
> +from lp.registry.interfaces.person import validate_person
> +from lp.registry.interfaces.role import IPersonRoles
> +from lp.services.database.constants import UTC_NOW
> +from lp.services.database.stormbase import StormBase
> +from lp.snappy.interfaces.snapsubscription import ISnapSubscription
> +
> +
> +@implementer(ISnapSubscription)
> +class SnapSubscription(StormBase):
> + """A relationship between a person and a snap recipe."""
> +
> + __storm_table__ = 'SnapSubscription'
> +
> + id = Int(primary=True)
> +
> + person_id = Int(
> + "person", allow_none=False, validator=validate_person)
> + person = Reference(person_id, "Person.id")
> +
> + snap_id = Int("snap", allow_none=False)
> + snap = Reference(snap_id, "Snap.id")
> +
> + date_created = DateTime(allow_none=False, default=UTC_NOW, tzinfo=pytz.UTC)
> +
> + subscribed_by_id = Int(
> + "subscribed_by", allow_none=False, validator=validate_person)
> + subscribed_by = Reference(subscribed_by_id, "Person.id")
> +
> + def __init__(self, snap=None, person=None, subscribed_by=None):
Ok!
> + super(SnapSubscription, self).__init__()
> + self.snap = snap
> + self.person = person
> + self.subscribed_by = subscribed_by
> +
> + def canBeUnsubscribedByUser(self, user):
> + """See `ISnapSubscription`."""
> + if user is None:
> + return False
> + return (user.inTeam(self.person) or
> + user.inTeam(self.subscribed_by) or
> + IPersonRoles(user).in_admin)
Makes sense. Fixing it.
> diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py
> index e8ea61e..bfa4e0c 100644
> --- a/lib/lp/snappy/tests/test_snap.py
> +++ b/lib/lp/snappy/tests/test_snap.py
> @@ -1353,6 +1356,9 @@ class TestSnapVisibility(TestCaseWithFactory):
> AccessArtifactGrant.abstract_artifact_id == AccessArtifact.id,
> *conditions)
>
> + def getSnapSubscription(self, snap, person):
> + return removeSecurityProxy(snap)._getSubscription(person)
I ended up doing it in another MP further down the line, when managing the user's subscription from the UI: https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/398319.
> +
> def test_only_owner_can_grant_access(self):
> owner = self.factory.makePerson()
> snap = self.factory.makeSnap(
--
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/397755
Your team Launchpad code reviewers is subscribed to branch ~pappacena/launchpad:snap-pillar-reconcile-access.
References