Merge ~pappacena/launchpad:snap-pillar-subscribe-removal-job into launchpad:master

Proposed by Thiago F. Pappacena
Status: Superseded
Proposed branch: ~pappacena/launchpad:snap-pillar-subscribe-removal-job
Merge into: launchpad:master
Prerequisite: ~pappacena/launchpad:snap-pillar-subscribe
Diff against target: 890 lines (+199/-85)
16 files modified
database/schema/security.cfg (+2/-0)
lib/lp/blueprints/model/specification.py (+2/-2)
lib/lp/blueprints/tests/test_specification.py (+4/-4)
lib/lp/bugs/model/bug.py (+3/-3)
lib/lp/code/browser/branchsubscription.py (+2/-2)
lib/lp/code/browser/gitsubscription.py (+2/-2)
lib/lp/code/model/branch.py (+2/-2)
lib/lp/code/model/gitrepository.py (+1/-1)
lib/lp/code/model/tests/test_branchsubscription.py (+3/-3)
lib/lp/registry/model/sharingjob.py (+36/-1)
lib/lp/registry/services/sharingservice.py (+15/-12)
lib/lp/registry/services/tests/test_sharingservice.py (+11/-7)
lib/lp/registry/tests/test_sharingjob.py (+56/-10)
lib/lp/snappy/interfaces/snap.py (+4/-0)
lib/lp/snappy/model/snap.py (+36/-12)
lib/lp/snappy/tests/test_snap.py (+20/-24)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+398318@code.launchpad.net

This proposal has been superseded by a proposal from 2021-03-11.

Commit message

Remove dangling SnapSubscription when changing Snap pillar's sharing policy

To post a comment you must log in.
Revision history for this message
Thiago F. Pappacena (pappacena) :
Revision history for this message
Colin Watson (cjwatson) :
6ead4b4... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

96846ab... by Thiago F. Pappacena

Fixing snap privacy filter usage

be29706... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

0efaed4... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

d256e68... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

Revision history for this message
Colin Watson (cjwatson) :
review: Approve
76b720d... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

e857291... by Thiago F. Pappacena

Refactoring and setting proper db permission for job

Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

Pushed the requested changes, and added a comment about `get_snap_privacy_filter` method.

Revision history for this message
Colin Watson (cjwatson) :
920b88c... by Thiago F. Pappacena

Not granting commercial admin view permisson on every snap

Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

Pushed the changes for admin visibility after talking to wgrant and cjwatson.

Revision history for this message
Colin Watson (cjwatson) :
review: Approve
df76888... by Thiago F. Pappacena

Merge branch 'master' into snap-pillar-subscribe-removal-job

5f729ee... by Thiago F. Pappacena

Removing unecessary shortcut for admin permission on snaps

Revision history for this message
Thiago F. Pappacena (pappacena) :

Unmerged commits

5f729ee... by Thiago F. Pappacena

Removing unecessary shortcut for admin permission on snaps

df76888... by Thiago F. Pappacena

Merge branch 'master' into snap-pillar-subscribe-removal-job

920b88c... by Thiago F. Pappacena

Not granting commercial admin view permisson on every snap

e857291... by Thiago F. Pappacena

Refactoring and setting proper db permission for job

76b720d... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

d256e68... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

0efaed4... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

be29706... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

96846ab... by Thiago F. Pappacena

Fixing snap privacy filter usage

6ead4b4... by Thiago F. Pappacena

Merge branch 'snap-pillar-subscribe' into snap-pillar-subscribe-removal-job

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/database/schema/security.cfg b/database/schema/security.cfg
index e9dcc62..2f4ffdf 100644
--- a/database/schema/security.cfg
+++ b/database/schema/security.cfg
@@ -2111,6 +2111,8 @@ public.job = SELECT, INSERT, UPDATE
2111public.person = SELECT2111public.person = SELECT
2112public.product = SELECT2112public.product = SELECT
2113public.sharingjob = SELECT, INSERT, UPDATE2113public.sharingjob = SELECT, INSERT, UPDATE
2114public.snap = SELECT
2115public.snapsubscription = SELECT, UPDATE, DELETE
2114public.specification = SELECT2116public.specification = SELECT
2115public.specificationsubscription = SELECT, DELETE2117public.specificationsubscription = SELECT, DELETE
2116public.teamparticipation = SELECT2118public.teamparticipation = SELECT
diff --git a/lib/lp/blueprints/model/specification.py b/lib/lp/blueprints/model/specification.py
index e0d4001..55ce9d5 100644
--- a/lib/lp/blueprints/model/specification.py
+++ b/lib/lp/blueprints/model/specification.py
@@ -1,4 +1,4 @@
1# Copyright 2009-2020 Canonical Ltd. This software is licensed under the1# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -755,7 +755,7 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
755 # Grant the subscriber access if they can't see the755 # Grant the subscriber access if they can't see the
756 # specification.756 # specification.
757 service = getUtility(IService, 'sharing')757 service = getUtility(IService, 'sharing')
758 _, _, _, shared_specs = service.getVisibleArtifacts(758 _, _, _, _, shared_specs = service.getVisibleArtifacts(
759 person, specifications=[self], ignore_permissions=True)759 person, specifications=[self], ignore_permissions=True)
760 if not shared_specs:760 if not shared_specs:
761 service.ensureAccessGrants(761 service.ensureAccessGrants(
diff --git a/lib/lp/blueprints/tests/test_specification.py b/lib/lp/blueprints/tests/test_specification.py
index 8de08c9..86c4e4a 100644
--- a/lib/lp/blueprints/tests/test_specification.py
+++ b/lib/lp/blueprints/tests/test_specification.py
@@ -1,4 +1,4 @@
1# Copyright 2010-2015 Canonical Ltd. This software is licensed under the1# Copyright 2010-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Unit tests for Specification."""4"""Unit tests for Specification."""
@@ -492,7 +492,7 @@ class SpecificationTests(TestCaseWithFactory):
492 product=product, information_type=InformationType.PROPRIETARY)492 product=product, information_type=InformationType.PROPRIETARY)
493 spec.subscribe(user, subscribed_by=owner)493 spec.subscribe(user, subscribed_by=owner)
494 service = getUtility(IService, 'sharing')494 service = getUtility(IService, 'sharing')
495 _, _, _, shared_specs = service.getVisibleArtifacts(495 _, _, _, _, shared_specs = service.getVisibleArtifacts(
496 user, specifications=[spec])496 user, specifications=[spec])
497 self.assertEqual([spec], shared_specs)497 self.assertEqual([spec], shared_specs)
498 # The spec is also returned by getSharedSpecifications(),498 # The spec is also returned by getSharedSpecifications(),
@@ -509,7 +509,7 @@ class SpecificationTests(TestCaseWithFactory):
509 service.sharePillarInformation(509 service.sharePillarInformation(
510 product, user_2, owner, permissions)510 product, user_2, owner, permissions)
511 spec.subscribe(user_2, subscribed_by=owner)511 spec.subscribe(user_2, subscribed_by=owner)
512 _, _, _, shared_specs = service.getVisibleArtifacts(512 _, _, _, _, shared_specs = service.getVisibleArtifacts(
513 user_2, specifications=[spec])513 user_2, specifications=[spec])
514 self.assertEqual([spec], shared_specs)514 self.assertEqual([spec], shared_specs)
515 self.assertEqual(515 self.assertEqual(
@@ -529,7 +529,7 @@ class SpecificationTests(TestCaseWithFactory):
529 spec.subscribe(user, subscribed_by=owner)529 spec.subscribe(user, subscribed_by=owner)
530 spec.unsubscribe(user, unsubscribed_by=owner)530 spec.unsubscribe(user, unsubscribed_by=owner)
531 service = getUtility(IService, 'sharing')531 service = getUtility(IService, 'sharing')
532 _, _, _, shared_specs = service.getVisibleArtifacts(532 _, _, _, _, shared_specs = service.getVisibleArtifacts(
533 user, specifications=[spec])533 user, specifications=[spec])
534 self.assertEqual([], shared_specs)534 self.assertEqual([], shared_specs)
535535
diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py
index a9b177c..f7ebc86 100644
--- a/lib/lp/bugs/model/bug.py
+++ b/lib/lp/bugs/model/bug.py
@@ -1,4 +1,4 @@
1# Copyright 2009-2020 Canonical Ltd. This software is licensed under the1# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Launchpad bug-related database table classes."""4"""Launchpad bug-related database table classes."""
@@ -875,7 +875,7 @@ class Bug(SQLBase, InformationTypeMixin):
875 # there is at least one bugtask for which access can be checked.875 # there is at least one bugtask for which access can be checked.
876 if self.default_bugtask:876 if self.default_bugtask:
877 service = getUtility(IService, 'sharing')877 service = getUtility(IService, 'sharing')
878 bugs, _, _, _ = service.getVisibleArtifacts(878 bugs, _, _, _, _ = service.getVisibleArtifacts(
879 person, bugs=[self], ignore_permissions=True)879 person, bugs=[self], ignore_permissions=True)
880 if not bugs:880 if not bugs:
881 service.ensureAccessGrants(881 service.ensureAccessGrants(
@@ -1819,7 +1819,7 @@ class Bug(SQLBase, InformationTypeMixin):
1819 if information_type in PRIVATE_INFORMATION_TYPES:1819 if information_type in PRIVATE_INFORMATION_TYPES:
1820 service = getUtility(IService, 'sharing')1820 service = getUtility(IService, 'sharing')
1821 for person in (who, self.owner):1821 for person in (who, self.owner):
1822 bugs, _, _, _ = service.getVisibleArtifacts(1822 bugs, _, _, _, _ = service.getVisibleArtifacts(
1823 person, bugs=[self], ignore_permissions=True)1823 person, bugs=[self], ignore_permissions=True)
1824 if not bugs:1824 if not bugs:
1825 # subscribe() isn't sufficient if a subscription1825 # subscribe() isn't sufficient if a subscription
diff --git a/lib/lp/code/browser/branchsubscription.py b/lib/lp/code/browser/branchsubscription.py
index fbd98bc..cee4504 100644
--- a/lib/lp/code/browser/branchsubscription.py
+++ b/lib/lp/code/browser/branchsubscription.py
@@ -1,4 +1,4 @@
1# Copyright 2009-2020 Canonical Ltd. This software is licensed under the1# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -276,7 +276,7 @@ class BranchSubscriptionEditView(LaunchpadEditFormView):
276 url = canonical_url(self.branch)276 url = canonical_url(self.branch)
277 # If the subscriber can no longer see the branch, redirect them away.277 # If the subscriber can no longer see the branch, redirect them away.
278 service = getUtility(IService, 'sharing')278 service = getUtility(IService, 'sharing')
279 _, branches, _, _ = service.getVisibleArtifacts(279 _, branches, _, _, _ = service.getVisibleArtifacts(
280 self.person, branches=[self.branch], ignore_permissions=True)280 self.person, branches=[self.branch], ignore_permissions=True)
281 if not branches:281 if not branches:
282 url = canonical_url(self.branch.target)282 url = canonical_url(self.branch.target)
diff --git a/lib/lp/code/browser/gitsubscription.py b/lib/lp/code/browser/gitsubscription.py
index 3eda78c..a18d593 100644
--- a/lib/lp/code/browser/gitsubscription.py
+++ b/lib/lp/code/browser/gitsubscription.py
@@ -1,4 +1,4 @@
1# Copyright 2015-2018 Canonical Ltd. This software is licensed under the1# Copyright 2015-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -280,7 +280,7 @@ class GitSubscriptionEditView(LaunchpadEditFormView):
280 # If the subscriber can no longer see the repository, redirect them280 # If the subscriber can no longer see the repository, redirect them
281 # away.281 # away.
282 service = getUtility(IService, "sharing")282 service = getUtility(IService, "sharing")
283 _, _, repositories, _ = service.getVisibleArtifacts(283 _, _, repositories, _, _ = service.getVisibleArtifacts(
284 self.person, gitrepositories=[self.repository],284 self.person, gitrepositories=[self.repository],
285 ignore_permissions=True)285 ignore_permissions=True)
286 if not repositories:286 if not repositories:
diff --git a/lib/lp/code/model/branch.py b/lib/lp/code/model/branch.py
index 947aaf1..278db40 100644
--- a/lib/lp/code/model/branch.py
+++ b/lib/lp/code/model/branch.py
@@ -1,4 +1,4 @@
1# Copyright 2009-2020 Canonical Ltd. This software is licensed under the1# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -1041,7 +1041,7 @@ class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin):
1041 subscription.review_level = code_review_level1041 subscription.review_level = code_review_level
1042 # Grant the subscriber access if they can't see the branch.1042 # Grant the subscriber access if they can't see the branch.
1043 service = getUtility(IService, 'sharing')1043 service = getUtility(IService, 'sharing')
1044 _, branches, _, _ = service.getVisibleArtifacts(1044 _, branches, _, _, _ = service.getVisibleArtifacts(
1045 person, branches=[self], ignore_permissions=True)1045 person, branches=[self], ignore_permissions=True)
1046 if not branches:1046 if not branches:
1047 service.ensureAccessGrants(1047 service.ensureAccessGrants(
diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py
index 74d94ec..589dcbf 100644
--- a/lib/lp/code/model/gitrepository.py
+++ b/lib/lp/code/model/gitrepository.py
@@ -1002,7 +1002,7 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin):
1002 subscription.review_level = code_review_level1002 subscription.review_level = code_review_level
1003 # Grant the subscriber access if they can't see the repository.1003 # Grant the subscriber access if they can't see the repository.
1004 service = getUtility(IService, "sharing")1004 service = getUtility(IService, "sharing")
1005 _, _, repositories, _ = service.getVisibleArtifacts(1005 _, _, repositories, _, _ = service.getVisibleArtifacts(
1006 person, gitrepositories=[self], ignore_permissions=True)1006 person, gitrepositories=[self], ignore_permissions=True)
1007 if not repositories:1007 if not repositories:
1008 service.ensureAccessGrants(1008 service.ensureAccessGrants(
diff --git a/lib/lp/code/model/tests/test_branchsubscription.py b/lib/lp/code/model/tests/test_branchsubscription.py
index 6a15cd0..b5b234e 100644
--- a/lib/lp/code/model/tests/test_branchsubscription.py
+++ b/lib/lp/code/model/tests/test_branchsubscription.py
@@ -1,4 +1,4 @@
1# Copyright 2010-2017 Canonical Ltd. This software is licensed under the1# Copyright 2010-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for the BranchSubscription model object."""4"""Tests for the BranchSubscription model object."""
@@ -134,7 +134,7 @@ class TestBranchSubscriptions(TestCaseWithFactory):
134 None, CodeReviewNotificationLevel.NOEMAIL, owner)134 None, CodeReviewNotificationLevel.NOEMAIL, owner)
135 # The stacked on branch should be visible.135 # The stacked on branch should be visible.
136 service = getUtility(IService, 'sharing')136 service = getUtility(IService, 'sharing')
137 _, visible_branches, _, _ = service.getVisibleArtifacts(137 _, visible_branches, _, _, _ = service.getVisibleArtifacts(
138 grantee, branches=[private_stacked_on_branch])138 grantee, branches=[private_stacked_on_branch])
139 self.assertContentEqual(139 self.assertContentEqual(
140 [private_stacked_on_branch], visible_branches)140 [private_stacked_on_branch], visible_branches)
@@ -162,7 +162,7 @@ class TestBranchSubscriptions(TestCaseWithFactory):
162 grantee, BranchSubscriptionNotificationLevel.NOEMAIL,162 grantee, BranchSubscriptionNotificationLevel.NOEMAIL,
163 None, CodeReviewNotificationLevel.NOEMAIL, owner)163 None, CodeReviewNotificationLevel.NOEMAIL, owner)
164 # The stacked on branch should not be visible.164 # The stacked on branch should not be visible.
165 _, visible_branches, _, _ = service.getVisibleArtifacts(165 _, visible_branches, _, _, _ = service.getVisibleArtifacts(
166 grantee, branches=[private_stacked_on_branch])166 grantee, branches=[private_stacked_on_branch])
167 self.assertContentEqual([], visible_branches)167 self.assertContentEqual([], visible_branches)
168 self.assertIn(168 self.assertIn(
diff --git a/lib/lp/registry/model/sharingjob.py b/lib/lp/registry/model/sharingjob.py
index 7d10fa9..81e6a9e 100644
--- a/lib/lp/registry/model/sharingjob.py
+++ b/lib/lp/registry/model/sharingjob.py
@@ -1,4 +1,4 @@
1# Copyright 2012-2020 Canonical Ltd. This software is licensed under the1# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Job classes related to the sharing feature are in here."""4"""Job classes related to the sharing feature are in here."""
@@ -91,6 +91,12 @@ from lp.services.job.model.job import (
91 )91 )
92from lp.services.job.runner import BaseRunnableJob92from lp.services.job.runner import BaseRunnableJob
93from lp.services.mail.sendmail import format_address_for_person93from lp.services.mail.sendmail import format_address_for_person
94from lp.snappy.interfaces.snap import ISnap
95from lp.snappy.model.snap import (
96 get_snap_privacy_filter,
97 Snap,
98 )
99from lp.snappy.model.snapsubscription import SnapSubscription
94100
95101
96class SharingJobType(DBEnumeratedType):102class SharingJobType(DBEnumeratedType):
@@ -263,6 +269,7 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
263 bug_ids = []269 bug_ids = []
264 branch_ids = []270 branch_ids = []
265 gitrepository_ids = []271 gitrepository_ids = []
272 snap_ids = []
266 specification_ids = []273 specification_ids = []
267 if artifacts:274 if artifacts:
268 for artifact in artifacts:275 for artifact in artifacts:
@@ -272,6 +279,8 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
272 branch_ids.append(artifact.id)279 branch_ids.append(artifact.id)
273 elif IGitRepository.providedBy(artifact):280 elif IGitRepository.providedBy(artifact):
274 gitrepository_ids.append(artifact.id)281 gitrepository_ids.append(artifact.id)
282 elif ISnap.providedBy(artifact):
283 snap_ids.append(artifact.id)
275 elif ISpecification.providedBy(artifact):284 elif ISpecification.providedBy(artifact):
276 specification_ids.append(artifact.id)285 specification_ids.append(artifact.id)
277 else:286 else:
@@ -284,6 +293,7 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
284 'bug_ids': bug_ids,293 'bug_ids': bug_ids,
285 'branch_ids': branch_ids,294 'branch_ids': branch_ids,
286 'gitrepository_ids': gitrepository_ids,295 'gitrepository_ids': gitrepository_ids,
296 'snap_ids': snap_ids,
287 'specification_ids': specification_ids,297 'specification_ids': specification_ids,
288 'information_types': information_types,298 'information_types': information_types,
289 'requestor.id': requestor.id299 'requestor.id': requestor.id
@@ -320,6 +330,10 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
320 return self.metadata.get('gitrepository_ids', [])330 return self.metadata.get('gitrepository_ids', [])
321331
322 @property332 @property
333 def snap_ids(self):
334 return self.metadata.get('snap_ids', [])
335
336 @property
323 def specification_ids(self):337 def specification_ids(self):
324 return self.metadata.get('specification_ids', [])338 return self.metadata.get('specification_ids', [])
325339
@@ -349,6 +363,7 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
349 'bug_ids': self.bug_ids,363 'bug_ids': self.bug_ids,
350 'branch_ids': self.branch_ids,364 'branch_ids': self.branch_ids,
351 'gitrepository_ids': self.gitrepository_ids,365 'gitrepository_ids': self.gitrepository_ids,
366 'snap_ids': self.snap_ids,
352 'specification_ids': self.specification_ids,367 'specification_ids': self.specification_ids,
353 'pillar': getattr(self.pillar, 'name', None),368 'pillar': getattr(self.pillar, 'name', None),
354 'grantee': getattr(self.grantee, 'name', None)369 'grantee': getattr(self.grantee, 'name', None)
@@ -365,6 +380,7 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
365 bug_filters = []380 bug_filters = []
366 branch_filters = []381 branch_filters = []
367 gitrepository_filters = []382 gitrepository_filters = []
383 snap_filters = []
368 specification_filters = []384 specification_filters = []
369385
370 if self.branch_ids:386 if self.branch_ids:
@@ -372,6 +388,8 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
372 if self.gitrepository_ids:388 if self.gitrepository_ids:
373 gitrepository_filters.append(GitRepository.id.is_in(389 gitrepository_filters.append(GitRepository.id.is_in(
374 self.gitrepository_ids))390 self.gitrepository_ids))
391 if self.snap_ids:
392 snap_filters.append(Snap.id.is_in(self.snap_ids))
375 if self.specification_ids:393 if self.specification_ids:
376 specification_filters.append(Specification.id.is_in(394 specification_filters.append(Specification.id.is_in(
377 self.specification_ids))395 self.specification_ids))
@@ -387,6 +405,8 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
387 gitrepository_filters.append(405 gitrepository_filters.append(
388 GitRepository.information_type.is_in(406 GitRepository.information_type.is_in(
389 self.information_types))407 self.information_types))
408 snap_filters.append(Snap._information_type.is_in(
409 self.information_types))
390 specification_filters.append(410 specification_filters.append(
391 Specification.information_type.is_in(411 Specification.information_type.is_in(
392 self.information_types))412 self.information_types))
@@ -423,6 +443,11 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
423 Select(443 Select(
424 TeamParticipation.personID,444 TeamParticipation.personID,
425 where=TeamParticipation.team == self.grantee)))445 where=TeamParticipation.team == self.grantee)))
446 snap_filters.append(
447 In(SnapSubscription.person_id,
448 Select(
449 TeamParticipation.personID,
450 where=TeamParticipation.team == self.grantee)))
426 specification_filters.append(451 specification_filters.append(
427 In(SpecificationSubscription.person_id,452 In(SpecificationSubscription.person_id,
428 Select(453 Select(
@@ -466,6 +491,16 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
466 for sub in gitrepository_subscriptions:491 for sub in gitrepository_subscriptions:
467 sub.repository.unsubscribe(492 sub.repository.unsubscribe(
468 sub.person, self.requestor, ignore_permissions=True)493 sub.person, self.requestor, ignore_permissions=True)
494 if snap_filters:
495 snap_filters.append(
496 Not(get_snap_privacy_filter(SnapSubscription.person_id)))
497 snap_subscriptions = IStore(SnapSubscription).using(
498 SnapSubscription,
499 Join(Snap, Snap.id == SnapSubscription.snap_id)
500 ).find(SnapSubscription, *snap_filters).config(distinct=True)
501 for sub in snap_subscriptions:
502 sub.snap.unsubscribe(
503 sub.person, self.requestor, ignore_permissions=True)
469 if specification_filters:504 if specification_filters:
470 specification_filters.append(Not(*get_specification_privacy_filter(505 specification_filters.append(Not(*get_specification_privacy_filter(
471 SpecificationSubscription.person_id)))506 SpecificationSubscription.person_id)))
diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py
index 01e90da..4909604 100644
--- a/lib/lp/registry/services/sharingservice.py
+++ b/lib/lp/registry/services/sharingservice.py
@@ -81,10 +81,7 @@ from lp.services.webapp.authorization import (
81 available_with_permission,81 available_with_permission,
82 check_permission,82 check_permission,
83 )83 )
84from lp.snappy.interfaces.snap import (84from lp.snappy.interfaces.snap import ISnapSet
85 ISnap,
86 ISnapSet,
87 )
8885
8986
90@implementer(ISharingService)87@implementer(ISharingService)
@@ -332,6 +329,7 @@ class SharingService:
332 bug_ids = []329 bug_ids = []
333 branch_ids = []330 branch_ids = []
334 gitrepository_ids = []331 gitrepository_ids = []
332 snap_ids = []
335 for bug in bugs or []:333 for bug in bugs or []:
336 if (not ignore_permissions334 if (not ignore_permissions
337 and not check_permission('launchpad.View', bug)):335 and not check_permission('launchpad.View', bug)):
@@ -344,9 +342,14 @@ class SharingService:
344 branch_ids.append(branch.id)342 branch_ids.append(branch.id)
345 for gitrepository in gitrepositories or []:343 for gitrepository in gitrepositories or []:
346 if (not ignore_permissions344 if (not ignore_permissions
347 and not check_permission('launchpad.View', gitrepository)):345 and not check_permission('launchpad.View', gitrepository)):
348 raise Unauthorized346 raise Unauthorized
349 gitrepository_ids.append(gitrepository.id)347 gitrepository_ids.append(gitrepository.id)
348 for snap in snaps or []:
349 if (not ignore_permissions
350 and not check_permission('launchpad.View', snap)):
351 raise Unauthorized
352 snap_ids.append(snap.id)
350 for spec in specifications or []:353 for spec in specifications or []:
351 if (not ignore_permissions354 if (not ignore_permissions
352 and not check_permission('launchpad.View', spec)):355 and not check_permission('launchpad.View', spec)):
@@ -376,6 +379,12 @@ class SharingService:
376 visible_gitrepositories = list(379 visible_gitrepositories = list(
377 wanted_gitrepositories.getRepositories())380 wanted_gitrepositories.getRepositories())
378381
382 # Load the Snaps.
383 visible_snaps = []
384 if snap_ids:
385 visible_snaps = list(getUtility(ISnapSet).findByIds(
386 snap_ids, visible_by_user=person))
387
379 # Load the specifications.388 # Load the specifications.
380 visible_specs = []389 visible_specs = []
381 if specifications:390 if specifications:
@@ -387,7 +396,7 @@ class SharingService:
387396
388 return (397 return (
389 visible_bugs, visible_branches, visible_gitrepositories,398 visible_bugs, visible_branches, visible_gitrepositories,
390 visible_specs)399 visible_snaps, visible_specs)
391400
392 def getInvisibleArtifacts(self, person, bugs=None, branches=None,401 def getInvisibleArtifacts(self, person, bugs=None, branches=None,
393 gitrepositories=None):402 gitrepositories=None):
@@ -800,12 +809,6 @@ class SharingService:
800 getUtility(IAccessArtifactGrantSource).revokeByArtifact(809 getUtility(IAccessArtifactGrantSource).revokeByArtifact(
801 artifacts_to_delete, [grantee])810 artifacts_to_delete, [grantee])
802811
803 # XXX: Pappacena 2021-02-05: snaps should not trigger this job,
804 # since we do not have a "SnapSubscription" yet.
805 artifacts = [i for i in artifacts if not ISnap.providedBy(i)]
806 if not artifacts:
807 return
808
809 # Create a job to remove subscriptions for artifacts the grantee can no812 # Create a job to remove subscriptions for artifacts the grantee can no
810 # longer see.813 # longer see.
811 return getUtility(IRemoveArtifactSubscriptionsJobSource).create(814 return getUtility(IRemoveArtifactSubscriptionsJobSource).create(
diff --git a/lib/lp/registry/services/tests/test_sharingservice.py b/lib/lp/registry/services/tests/test_sharingservice.py
index 9d0e07c..8d94723 100644
--- a/lib/lp/registry/services/tests/test_sharingservice.py
+++ b/lib/lp/registry/services/tests/test_sharingservice.py
@@ -1,4 +1,4 @@
1# Copyright 2012-2015 Canonical Ltd. This software is licensed under the1# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -1101,7 +1101,7 @@ class TestSharingService(TestCaseWithFactory):
1101 # Check that grantees have expected access grants and subscriptions.1101 # Check that grantees have expected access grants and subscriptions.
1102 for person in [team_grantee, person_grantee]:1102 for person in [team_grantee, person_grantee]:
1103 (visible_bugs, visible_branches, visible_gitrepositories,1103 (visible_bugs, visible_branches, visible_gitrepositories,
1104 visible_specs) = (1104 visible_snaps, visible_specs) = (
1105 self.service.getVisibleArtifacts(1105 self.service.getVisibleArtifacts(
1106 person, bugs=bugs, branches=branches,1106 person, bugs=bugs, branches=branches,
1107 gitrepositories=gitrepositories,1107 gitrepositories=gitrepositories,
@@ -1133,7 +1133,7 @@ class TestSharingService(TestCaseWithFactory):
1133 for bug in bugs or []:1133 for bug in bugs or []:
1134 self.assertNotIn(person, bug.getDirectSubscribers())1134 self.assertNotIn(person, bug.getDirectSubscribers())
1135 (visible_bugs, visible_branches, visible_gitrepositories,1135 (visible_bugs, visible_branches, visible_gitrepositories,
1136 visible_specs) = (1136 visible_snaps, visible_specs) = (
1137 self.service.getVisibleArtifacts(1137 self.service.getVisibleArtifacts(
1138 person, bugs=bugs, branches=branches,1138 person, bugs=bugs, branches=branches,
1139 gitrepositories=gitrepositories))1139 gitrepositories=gitrepositories))
@@ -1783,7 +1783,8 @@ class TestSharingService(TestCaseWithFactory):
1783 grantee, ignore, bugs, branches, gitrepositories, specs = (1783 grantee, ignore, bugs, branches, gitrepositories, specs = (
1784 self._make_Artifacts())1784 self._make_Artifacts())
1785 # Check the results.1785 # Check the results.
1786 shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (1786 (shared_bugs, shared_branches, shared_gitrepositories,
1787 shared_snaps, shared_specs) = (
1787 self.service.getVisibleArtifacts(1788 self.service.getVisibleArtifacts(
1788 grantee, bugs=bugs, branches=branches,1789 grantee, bugs=bugs, branches=branches,
1789 gitrepositories=gitrepositories, specifications=specs))1790 gitrepositories=gitrepositories, specifications=specs))
@@ -1797,7 +1798,8 @@ class TestSharingService(TestCaseWithFactory):
1797 # user has a policy grant for the pillar of the specification.1798 # user has a policy grant for the pillar of the specification.
1798 _, owner, bugs, branches, gitrepositories, specs = (1799 _, owner, bugs, branches, gitrepositories, specs = (
1799 self._make_Artifacts())1800 self._make_Artifacts())
1800 shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (1801 (shared_bugs, shared_branches, shared_gitrepositories,
1802 shared_snaps, shared_specs) = (
1801 self.service.getVisibleArtifacts(1803 self.service.getVisibleArtifacts(
1802 owner, bugs=bugs, branches=branches,1804 owner, bugs=bugs, branches=branches,
1803 gitrepositories=gitrepositories, specifications=specs))1805 gitrepositories=gitrepositories, specifications=specs))
@@ -1840,7 +1842,8 @@ class TestSharingService(TestCaseWithFactory):
1840 information_type=InformationType.USERDATA)1842 information_type=InformationType.USERDATA)
1841 bugs.append(bug)1843 bugs.append(bug)
18421844
1843 shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (1845 (shared_bugs, shared_branches, shared_gitrepositories,
1846 visible_snaps, shared_specs) = (
1844 self.service.getVisibleArtifacts(grantee, bugs=bugs))1847 self.service.getVisibleArtifacts(grantee, bugs=bugs))
1845 self.assertContentEqual(bugs, shared_bugs)1848 self.assertContentEqual(bugs, shared_bugs)
18461849
@@ -1848,7 +1851,8 @@ class TestSharingService(TestCaseWithFactory):
1848 for x in range(0, 5):1851 for x in range(0, 5):
1849 change_callback(bugs[x], owner)1852 change_callback(bugs[x], owner)
1850 # Check the results.1853 # Check the results.
1851 shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (1854 (shared_bugs, shared_branches, shared_gitrepositories,
1855 visible_snaps, shared_specs) = (
1852 self.service.getVisibleArtifacts(grantee, bugs=bugs))1856 self.service.getVisibleArtifacts(grantee, bugs=bugs))
1853 self.assertContentEqual(bugs[5:], shared_bugs)1857 self.assertContentEqual(bugs[5:], shared_bugs)
18541858
diff --git a/lib/lp/registry/tests/test_sharingjob.py b/lib/lp/registry/tests/test_sharingjob.py
index 58fa5c1..32aec0f 100644
--- a/lib/lp/registry/tests/test_sharingjob.py
+++ b/lib/lp/registry/tests/test_sharingjob.py
@@ -1,4 +1,4 @@
1# Copyright 2012-2015 Canonical Ltd. This software is licensed under the1# Copyright 2012-2021 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for SharingJobs."""4"""Tests for SharingJobs."""
@@ -39,6 +39,7 @@ from lp.services.features.testing import FeatureFixture
39from lp.services.job.interfaces.job import JobStatus39from lp.services.job.interfaces.job import JobStatus
40from lp.services.job.tests import block_on_job40from lp.services.job.tests import block_on_job
41from lp.services.mail.sendmail import format_address_for_person41from lp.services.mail.sendmail import format_address_for_person
42from lp.snappy.interfaces.snap import SNAP_TESTING_FLAGS
42from lp.testing import (43from lp.testing import (
43 login_person,44 login_person,
44 person_logged_in,45 person_logged_in,
@@ -127,6 +128,16 @@ class SharingJobDerivedTestCase(TestCaseWithFactory):
127 'for gitrepository_ids=[%d], requestor=%s>'128 'for gitrepository_ids=[%d], requestor=%s>'
128 % (gitrepository.id, requestor.name), repr(job))129 % (gitrepository.id, requestor.name), repr(job))
129130
131 def test_repr_snaps(self):
132 requestor = self.factory.makePerson()
133 snap = self.factory.makeSnap()
134 job = getUtility(IRemoveArtifactSubscriptionsJobSource).create(
135 requestor, artifacts=[snap])
136 self.assertEqual(
137 '<REMOVE_ARTIFACT_SUBSCRIPTIONS job reconciling subscriptions '
138 'for requestor=%s, snap_ids=[%d]>'
139 % (requestor.name, snap.id), repr(job))
140
130 def test_repr_specifications(self):141 def test_repr_specifications(self):
131 requestor = self.factory.makePerson()142 requestor = self.factory.makePerson()
132 specification = self.factory.makeSpecification()143 specification = self.factory.makeSpecification()
@@ -241,9 +252,11 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
241 layer = CeleryJobLayer252 layer = CeleryJobLayer
242253
243 def setUp(self):254 def setUp(self):
244 self.useFixture(FeatureFixture({255 features = {
245 'jobs.celery.enabled_classes': 'RemoveArtifactSubscriptionsJob',256 'jobs.celery.enabled_classes': 'RemoveArtifactSubscriptionsJob',
246 }))257 }
258 features.update(SNAP_TESTING_FLAGS)
259 self.useFixture(FeatureFixture(features))
247 super(RemoveArtifactSubscriptionsJobTestCase, self).setUp()260 super(RemoveArtifactSubscriptionsJobTestCase, self).setUp()
248261
249 def test_create(self):262 def test_create(self):
@@ -315,6 +328,9 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
315 gitrepository = self.factory.makeGitRepository(328 gitrepository = self.factory.makeGitRepository(
316 owner=owner, target=product,329 owner=owner, target=product,
317 information_type=InformationType.USERDATA)330 information_type=InformationType.USERDATA)
331 snap = self.factory.makeSnap(
332 owner=owner, registrant=owner, project=product,
333 information_type=InformationType.USERDATA)
318 specification = self.factory.makeSpecification(334 specification = self.factory.makeSpecification(
319 owner=owner, product=product,335 owner=owner, product=product,
320 information_type=InformationType.PROPRIETARY)336 information_type=InformationType.PROPRIETARY)
@@ -332,6 +348,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
332 gitrepository.subscribe(artifact_indirect_grantee,348 gitrepository.subscribe(artifact_indirect_grantee,
333 BranchSubscriptionNotificationLevel.NOEMAIL, None,349 BranchSubscriptionNotificationLevel.NOEMAIL, None,
334 CodeReviewNotificationLevel.NOEMAIL, owner)350 CodeReviewNotificationLevel.NOEMAIL, owner)
351 snap.subscribe(artifact_indirect_grantee, owner)
335 # Subscribing somebody to a specification does not automatically352 # Subscribing somebody to a specification does not automatically
336 # create an artifact grant.353 # create an artifact grant.
337 spec_artifact = self.factory.makeAccessArtifact(specification)354 spec_artifact = self.factory.makeAccessArtifact(specification)
@@ -341,10 +358,11 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
341 specification.subscribe(artifact_indirect_grantee, owner)358 specification.subscribe(artifact_indirect_grantee, owner)
342359
343 # pick one of the concrete artifacts (bug, branch, Git repository,360 # pick one of the concrete artifacts (bug, branch, Git repository,
344 # or spec) and subscribe the teams and persons.361 # snap, or spec) and subscribe the teams and persons.
345 concrete_artifact, get_pillars, get_subscribers = configure_test(362 concrete_artifact, get_pillars, get_subscribers = configure_test(
346 bug, branch, gitrepository, specification, policy_team_grantee,363 bug, branch, gitrepository, snap, specification,
347 policy_indirect_grantee, artifact_team_grantee, owner)364 policy_team_grantee, policy_indirect_grantee,
365 artifact_team_grantee, owner)
348366
349 # Subscribing policy_team_grantee has created an artifact grant so we367 # Subscribing policy_team_grantee has created an artifact grant so we
350 # need to revoke that to test the job.368 # need to revoke that to test the job.
@@ -377,6 +395,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
377 self.assertIn(artifact_indirect_grantee, bug.getDirectSubscribers())395 self.assertIn(artifact_indirect_grantee, bug.getDirectSubscribers())
378 self.assertIn(artifact_indirect_grantee, branch.subscribers)396 self.assertIn(artifact_indirect_grantee, branch.subscribers)
379 self.assertIn(artifact_indirect_grantee, gitrepository.subscribers)397 self.assertIn(artifact_indirect_grantee, gitrepository.subscribers)
398 self.assertIn(artifact_indirect_grantee, snap.subscribers)
380 self.assertIn(artifact_indirect_grantee,399 self.assertIn(artifact_indirect_grantee,
381 removeSecurityProxy(specification).subscribers)400 removeSecurityProxy(specification).subscribers)
382401
@@ -389,7 +408,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
389 return removeSecurityProxy(408 return removeSecurityProxy(
390 concrete_artifact).getDirectSubscribers()409 concrete_artifact).getDirectSubscribers()
391410
392 def configure_test(bug, branch, gitrepository, specification,411 def configure_test(bug, branch, gitrepository, snap, specification,
393 policy_team_grantee, policy_indirect_grantee,412 policy_team_grantee, policy_indirect_grantee,
394 artifact_team_grantee, owner):413 artifact_team_grantee, owner):
395 concrete_artifact = bug414 concrete_artifact = bug
@@ -409,7 +428,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
409 def get_subscribers(concrete_artifact):428 def get_subscribers(concrete_artifact):
410 return concrete_artifact.subscribers429 return concrete_artifact.subscribers
411430
412 def configure_test(bug, branch, gitrepository, specification,431 def configure_test(bug, branch, gitrepository, snap, specification,
413 policy_team_grantee, policy_indirect_grantee,432 policy_team_grantee, policy_indirect_grantee,
414 artifact_team_grantee, owner):433 artifact_team_grantee, owner):
415 concrete_artifact = branch434 concrete_artifact = branch
@@ -438,7 +457,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
438 def get_subscribers(concrete_artifact):457 def get_subscribers(concrete_artifact):
439 return concrete_artifact.subscribers458 return concrete_artifact.subscribers
440459
441 def configure_test(bug, branch, gitrepository, specification,460 def configure_test(bug, branch, gitrepository, snap, specification,
442 policy_team_grantee, policy_indirect_grantee,461 policy_team_grantee, policy_indirect_grantee,
443 artifact_team_grantee, owner):462 artifact_team_grantee, owner):
444 concrete_artifact = gitrepository463 concrete_artifact = gitrepository
@@ -459,6 +478,26 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
459 self._assert_artifact_change_unsubscribes(478 self._assert_artifact_change_unsubscribes(
460 change_callback, configure_test)479 change_callback, configure_test)
461480
481 def _assert_snap_change_unsubscribes(self, change_callback):
482
483 def get_pillars(concrete_artifact):
484 return [concrete_artifact.project]
485
486 def get_subscribers(concrete_artifact):
487 return concrete_artifact.subscribers
488
489 def configure_test(bug, branch, gitrepository, snap, specification,
490 policy_team_grantee, policy_indirect_grantee,
491 artifact_team_grantee, owner):
492 concrete_artifact = snap
493 snap.subscribe(policy_team_grantee, owner)
494 snap.subscribe(policy_indirect_grantee, owner)
495 snap.subscribe(artifact_team_grantee, owner)
496 return concrete_artifact, get_pillars, get_subscribers
497
498 self._assert_artifact_change_unsubscribes(
499 change_callback, configure_test)
500
462 def _assert_specification_change_unsubscribes(self, change_callback):501 def _assert_specification_change_unsubscribes(self, change_callback):
463502
464 def get_pillars(concrete_artifact):503 def get_pillars(concrete_artifact):
@@ -467,7 +506,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
467 def get_subscribers(concrete_artifact):506 def get_subscribers(concrete_artifact):
468 return concrete_artifact.subscribers507 return concrete_artifact.subscribers
469508
470 def configure_test(bug, branch, gitrepository, specification,509 def configure_test(bug, branch, gitrepository, snap, specification,
471 policy_team_grantee, policy_indirect_grantee,510 policy_team_grantee, policy_indirect_grantee,
472 artifact_team_grantee, owner):511 artifact_team_grantee, owner):
473 naked_spec = removeSecurityProxy(specification)512 naked_spec = removeSecurityProxy(specification)
@@ -496,6 +535,13 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
496535
497 self._assert_gitrepository_change_unsubscribes(change_information_type)536 self._assert_gitrepository_change_unsubscribes(change_information_type)
498537
538 def test_change_information_type_snap(self):
539 def change_information_type(snap):
540 removeSecurityProxy(snap).information_type = (
541 InformationType.PRIVATESECURITY)
542
543 self._assert_snap_change_unsubscribes(change_information_type)
544
499 def test_change_information_type_specification(self):545 def test_change_information_type_specification(self):
500 def change_information_type(specification):546 def change_information_type(specification):
501 removeSecurityProxy(specification).information_type = (547 removeSecurityProxy(specification).information_type = (
diff --git a/lib/lp/snappy/interfaces/snap.py b/lib/lp/snappy/interfaces/snap.py
index 31708c3..71f534b 100644
--- a/lib/lp/snappy/interfaces/snap.py
+++ b/lib/lp/snappy/interfaces/snap.py
@@ -571,6 +571,10 @@ class ISnapView(Interface):
571 # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.571 # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
572 value_type=Reference(schema=Interface), readonly=True)))572 value_type=Reference(schema=Interface), readonly=True)))
573573
574 subscribers = CollectionField(
575 title=_("Persons subscribed to this snap recipe."),
576 readonly=True, value_type=Reference(IPerson))
577
574 def visibleByUser(user):578 def visibleByUser(user):
575 """Can the specified user see this snap recipe?"""579 """Can the specified user see this snap recipe?"""
576580
diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py
index 141f27a..ba89613 100644
--- a/lib/lp/snappy/model/snap.py
+++ b/lib/lp/snappy/model/snap.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import, print_function, unicode_literals
55
6__metaclass__ = type6__metaclass__ = type
7__all__ = [7__all__ = [
8 'get_snap_privacy_filter',
8 'Snap',9 'Snap',
9 ]10 ]
1011
@@ -26,6 +27,7 @@ from storm.expr import (
26 And,27 And,
27 Coalesce,28 Coalesce,
28 Desc,29 Desc,
30 Exists,
29 Join,31 Join,
30 LeftJoin,32 LeftJoin,
31 Not,33 Not,
@@ -71,6 +73,7 @@ from lp.app.errors import (
71 SubscriptionPrivacyViolation,73 SubscriptionPrivacyViolation,
72 UserCannotUnsubscribePerson,74 UserCannotUnsubscribePerson,
73 )75 )
76from lp.app.interfaces.launchpad import ILaunchpadCelebrities
74from lp.app.interfaces.security import IAuthorization77from lp.app.interfaces.security import IAuthorization
75from lp.app.interfaces.services import IService78from lp.app.interfaces.services import IService
76from lp.buildmaster.enums import BuildStatus79from lp.buildmaster.enums import BuildStatus
@@ -132,6 +135,7 @@ from lp.registry.model.accesspolicy import (
132 reconcile_access_for_artifact,135 reconcile_access_for_artifact,
133 )136 )
134from lp.registry.model.distroseries import DistroSeries137from lp.registry.model.distroseries import DistroSeries
138from lp.registry.model.person import Person
135from lp.registry.model.series import ACTIVE_STATUSES139from lp.registry.model.series import ACTIVE_STATUSES
136from lp.registry.model.teammembership import TeamParticipation140from lp.registry.model.teammembership import TeamParticipation
137from lp.services.config import config141from lp.services.config import config
@@ -1165,6 +1169,13 @@ class Snap(Storm, WebhookTargetMixin):
1165 person.is_team and1169 person.is_team and
1166 person.anyone_can_join())1170 person.anyone_can_join())
11671171
1172 @property
1173 def subscribers(self):
1174 return Store.of(self).find(
1175 Person,
1176 SnapSubscription.person_id == Person.id,
1177 SnapSubscription.snap == self)
1178
1168 def subscribe(self, person, subscribed_by, ignore_permissions=False):1179 def subscribe(self, person, subscribed_by, ignore_permissions=False):
1169 """See `ISnap`."""1180 """See `ISnap`."""
1170 if not self._userCanBeSubscribed(person):1181 if not self._userCanBeSubscribed(person):
@@ -1177,9 +1188,12 @@ class Snap(Storm, WebhookTargetMixin):
1177 person=person, snap=self, subscribed_by=subscribed_by)1188 person=person, snap=self, subscribed_by=subscribed_by)
1178 Store.of(subscription).flush()1189 Store.of(subscription).flush()
1179 service = getUtility(IService, "sharing")1190 service = getUtility(IService, "sharing")
1180 service.ensureAccessGrants(1191 _, _, _, snaps, _ = service.getVisibleArtifacts(
1181 [person], subscribed_by, snaps=[self],1192 person, snaps=[self], ignore_permissions=True)
1182 ignore_permissions=ignore_permissions)1193 if not snaps:
1194 service.ensureAccessGrants(
1195 [person], subscribed_by, snaps=[self],
1196 ignore_permissions=ignore_permissions)
11831197
1184 def unsubscribe(self, person, unsubscribed_by, ignore_permissions=False):1198 def unsubscribe(self, person, unsubscribed_by, ignore_permissions=False):
1185 """See `ISnap`."""1199 """See `ISnap`."""
@@ -1421,9 +1435,12 @@ class SnapSet:
1421 expressions.append(Snap.owner == owner)1435 expressions.append(Snap.owner == owner)
1422 return IStore(Snap).find(Snap, *expressions)1436 return IStore(Snap).find(Snap, *expressions)
14231437
1424 def findByIds(self, snap_ids):1438 def findByIds(self, snap_ids, visible_by_user=None):
1425 """See `ISnapSet`."""1439 """See `ISnapSet`."""
1426 return IStore(ISnap).find(Snap, Snap.id.is_in(snap_ids))1440 clauses = [Snap.id.is_in(snap_ids)]
1441 if visible_by_user is not None:
1442 clauses.append(get_snap_privacy_filter(visible_by_user))
1443 return IStore(Snap).find(Snap, *clauses)
14271444
1428 def findByOwner(self, owner):1445 def findByOwner(self, owner):
1429 """See `ISnapSet`."""1446 """See `ISnapSet`."""
@@ -1694,9 +1711,11 @@ class SnapStoreSecretsEncryptedContainer(NaClEncryptedContainerBase):
16941711
16951712
1696def get_snap_privacy_filter(user):1713def get_snap_privacy_filter(user):
1697 """Returns the filter for all private Snaps that the given user is1714 """Returns the filter for all Snaps that the given user has access to,
1698 subscribed to (that is, has access without being directly an owner).1715 including private snaps where the user has proper permission.
16991716
1717 :param user: An IPerson, or a class attribute that references an IPerson
1718 in the database.
1700 :return: A storm condition.1719 :return: A storm condition.
1701 """1720 """
1702 # XXX pappacena 2021-02-12: Once we do the migration to back fill1721 # XXX pappacena 2021-02-12: Once we do the migration to back fill
@@ -1707,10 +1726,6 @@ def get_snap_privacy_filter(user):
1707 if user is None:1726 if user is None:
1708 return private_snap == False1727 return private_snap == False
17091728
1710 roles = IPersonRoles(user)
1711 if roles.in_admin or roles.in_commercial_admin:
1712 return True
1713
1714 artifact_grant_query = Coalesce(1729 artifact_grant_query = Coalesce(
1715 ArrayIntersects(1730 ArrayIntersects(
1716 SQL("%s.access_grants" % Snap.__storm_table__),1731 SQL("%s.access_grants" % Snap.__storm_table__),
@@ -1732,4 +1747,13 @@ def get_snap_privacy_filter(user):
1732 where=(TeamParticipation.person == user)1747 where=(TeamParticipation.person == user)
1733 )), False)1748 )), False)
17341749
1735 return Or(private_snap == False, artifact_grant_query, policy_grant_query)1750 admin_team_id = getUtility(ILaunchpadCelebrities).admin.id
1751 user_is_admin = Exists(Select(
1752 TeamParticipation.personID,
1753 tables=[TeamParticipation],
1754 where=And(
1755 TeamParticipation.teamID == admin_team_id,
1756 TeamParticipation.person == user)))
1757 return Or(
1758 private_snap == False, artifact_grant_query, policy_grant_query,
1759 user_is_admin)
diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py
index 39362b3..04ed945 100644
--- a/lib/lp/snappy/tests/test_snap.py
+++ b/lib/lp/snappy/tests/test_snap.py
@@ -3102,8 +3102,7 @@ class TestSnapWebservice(TestCaseWithFactory):
3102 ws_snaps = [3102 ws_snaps = [
3103 self.webservice.getAbsoluteUrl(api_url(snap))3103 self.webservice.getAbsoluteUrl(api_url(snap))
3104 for snap in snaps]3104 for snap in snaps]
3105 commercial_admin = (3105 admin = getUtility(ILaunchpadCelebrities).admin.teamowner
3106 getUtility(ILaunchpadCelebrities).commercial_admin.teamowner)
3107 logout()3106 logout()
31083107
3109 # Anonymous requests can only see public snaps.3108 # Anonymous requests can only see public snaps.
@@ -3141,15 +3140,15 @@ class TestSnapWebservice(TestCaseWithFactory):
3141 [entry["self_link"] for entry in response.jsonBody()["entries"]])3140 [entry["self_link"] for entry in response.jsonBody()["entries"]])
31423141
3143 # Admins can see all snaps with this URL.3142 # Admins can see all snaps with this URL.
3144 commercial_admin_webservice = webservice_for_person(3143 admin_webservice = webservice_for_person(
3145 commercial_admin, permission=OAuthPermission.READ_PRIVATE)3144 admin, permission=OAuthPermission.READ_PRIVATE)
3146 response = commercial_admin_webservice.named_get(3145 response = admin_webservice.named_get(
3147 "/+snaps", "findByURL", url=urls[0], api_version="devel")3146 "/+snaps", "findByURL", url=urls[0], api_version="devel")
3148 self.assertEqual(200, response.status)3147 self.assertEqual(200, response.status)
3149 self.assertContentEqual(3148 self.assertContentEqual(
3150 ws_snaps[:4],3149 ws_snaps[:4],
3151 [entry["self_link"] for entry in response.jsonBody()["entries"]])3150 [entry["self_link"] for entry in response.jsonBody()["entries"]])
3152 response = commercial_admin_webservice.named_get(3151 response = admin_webservice.named_get(
3153 "/+snaps", "findByURL", url=urls[0], owner=person_urls[0],3152 "/+snaps", "findByURL", url=urls[0], owner=person_urls[0],
3154 api_version="devel")3153 api_version="devel")
3155 self.assertEqual(200, response.status)3154 self.assertEqual(200, response.status)
@@ -3180,8 +3179,7 @@ class TestSnapWebservice(TestCaseWithFactory):
3180 ws_snaps = [3179 ws_snaps = [
3181 self.webservice.getAbsoluteUrl(api_url(snap))3180 self.webservice.getAbsoluteUrl(api_url(snap))
3182 for snap in snaps]3181 for snap in snaps]
3183 commercial_admin = (3182 admin = getUtility(ILaunchpadCelebrities).admin.teamowner
3184 getUtility(ILaunchpadCelebrities).commercial_admin.teamowner)
3185 logout()3183 logout()
3186 prefix = "https://git.example.org/foo/"3184 prefix = "https://git.example.org/foo/"
31873185
@@ -3222,16 +3220,16 @@ class TestSnapWebservice(TestCaseWithFactory):
3222 [entry["self_link"] for entry in response.jsonBody()["entries"]])3220 [entry["self_link"] for entry in response.jsonBody()["entries"]])
32233221
3224 # Admins can see all snaps with this URL prefix.3222 # Admins can see all snaps with this URL prefix.
3225 commercial_admin_webservice = webservice_for_person(3223 admin_webservice = webservice_for_person(
3226 commercial_admin, permission=OAuthPermission.READ_PRIVATE)3224 admin, permission=OAuthPermission.READ_PRIVATE)
3227 response = commercial_admin_webservice.named_get(3225 response = admin_webservice.named_get(
3228 "/+snaps", "findByURLPrefix", url_prefix=prefix,3226 "/+snaps", "findByURLPrefix", url_prefix=prefix,
3229 api_version="devel")3227 api_version="devel")
3230 self.assertEqual(200, response.status)3228 self.assertEqual(200, response.status)
3231 self.assertContentEqual(3229 self.assertContentEqual(
3232 ws_snaps[:8],3230 ws_snaps[:8],
3233 [entry["self_link"] for entry in response.jsonBody()["entries"]])3231 [entry["self_link"] for entry in response.jsonBody()["entries"]])
3234 response = commercial_admin_webservice.named_get(3232 response = admin_webservice.named_get(
3235 "/+snaps", "findByURLPrefix", url_prefix=prefix,3233 "/+snaps", "findByURLPrefix", url_prefix=prefix,
3236 owner=person_urls[0], api_version="devel")3234 owner=person_urls[0], api_version="devel")
3237 self.assertEqual(200, response.status)3235 self.assertEqual(200, response.status)
@@ -3264,8 +3262,7 @@ class TestSnapWebservice(TestCaseWithFactory):
3264 ws_snaps = [3262 ws_snaps = [
3265 self.webservice.getAbsoluteUrl(api_url(snap))3263 self.webservice.getAbsoluteUrl(api_url(snap))
3266 for snap in snaps]3264 for snap in snaps]
3267 commercial_admin = (3265 admin = getUtility(ILaunchpadCelebrities).admin.teamowner
3268 getUtility(ILaunchpadCelebrities).commercial_admin.teamowner)
3269 logout()3266 logout()
3270 prefixes = [3267 prefixes = [
3271 "https://git.example.org/foo/", "https://git.example.org/bar/"]3268 "https://git.example.org/foo/", "https://git.example.org/bar/"]
@@ -3307,16 +3304,16 @@ class TestSnapWebservice(TestCaseWithFactory):
3307 [entry["self_link"] for entry in response.jsonBody()["entries"]])3304 [entry["self_link"] for entry in response.jsonBody()["entries"]])
33083305
3309 # Admins can see all snaps with any of these URL prefixes.3306 # Admins can see all snaps with any of these URL prefixes.
3310 commercial_admin_webservice = webservice_for_person(3307 admin_webservice = webservice_for_person(
3311 commercial_admin, permission=OAuthPermission.READ_PRIVATE)3308 admin, permission=OAuthPermission.READ_PRIVATE)
3312 response = commercial_admin_webservice.named_get(3309 response = admin_webservice.named_get(
3313 "/+snaps", "findByURLPrefixes", url_prefixes=prefixes,3310 "/+snaps", "findByURLPrefixes", url_prefixes=prefixes,
3314 api_version="devel")3311 api_version="devel")
3315 self.assertEqual(200, response.status)3312 self.assertEqual(200, response.status)
3316 self.assertContentEqual(3313 self.assertContentEqual(
3317 ws_snaps[:16],3314 ws_snaps[:16],
3318 [entry["self_link"] for entry in response.jsonBody()["entries"]])3315 [entry["self_link"] for entry in response.jsonBody()["entries"]])
3319 response = commercial_admin_webservice.named_get(3316 response = admin_webservice.named_get(
3320 "/+snaps", "findByURLPrefixes", url_prefixes=prefixes,3317 "/+snaps", "findByURLPrefixes", url_prefixes=prefixes,
3321 owner=person_urls[0], api_version="devel")3318 owner=person_urls[0], api_version="devel")
3322 self.assertEqual(200, response.status)3319 self.assertEqual(200, response.status)
@@ -3341,8 +3338,7 @@ class TestSnapWebservice(TestCaseWithFactory):
3341 ws_snaps = [3338 ws_snaps = [
3342 self.webservice.getAbsoluteUrl(api_url(snap))3339 self.webservice.getAbsoluteUrl(api_url(snap))
3343 for snap in snaps]3340 for snap in snaps]
3344 commercial_admin = (3341 admin = getUtility(ILaunchpadCelebrities).admin.teamowner
3345 getUtility(ILaunchpadCelebrities).commercial_admin.teamowner)
3346 logout()3342 logout()
33473343
3348 # Anonymous requests can only see public snaps.3344 # Anonymous requests can only see public snaps.
@@ -3382,16 +3378,16 @@ class TestSnapWebservice(TestCaseWithFactory):
3382 [entry["self_link"] for entry in response.jsonBody()["entries"]])3378 [entry["self_link"] for entry in response.jsonBody()["entries"]])
33833379
3384 # Admins can see all snaps with this store name.3380 # Admins can see all snaps with this store name.
3385 commercial_admin_webservice = webservice_for_person(3381 admin_webservice = webservice_for_person(
3386 commercial_admin, permission=OAuthPermission.READ_PRIVATE)3382 admin, permission=OAuthPermission.READ_PRIVATE)
3387 response = commercial_admin_webservice.named_get(3383 response = admin_webservice.named_get(
3388 "/+snaps", "findByStoreName", store_name=store_names[0],3384 "/+snaps", "findByStoreName", store_name=store_names[0],
3389 api_version="devel")3385 api_version="devel")
3390 self.assertEqual(200, response.status)3386 self.assertEqual(200, response.status)
3391 self.assertContentEqual(3387 self.assertContentEqual(
3392 ws_snaps[:4],3388 ws_snaps[:4],
3393 [entry["self_link"] for entry in response.jsonBody()["entries"]])3389 [entry["self_link"] for entry in response.jsonBody()["entries"]])
3394 response = commercial_admin_webservice.named_get(3390 response = admin_webservice.named_get(
3395 "/+snaps", "findByStoreName", store_name=store_names[0],3391 "/+snaps", "findByStoreName", store_name=store_names[0],
3396 owner=person_urls[0], api_version="devel")3392 owner=person_urls[0], api_version="devel")
3397 self.assertEqual(200, response.status)3393 self.assertEqual(200, response.status)