Merge ~cjwatson/launchpad:webhook-team-owned-private-artifacts into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 7e523fa160724e8e2c0e450cab3f376722a4d06a
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:webhook-team-owned-private-artifacts
Merge into: launchpad:master
Diff against target: 108 lines (+16/-10)
3 files modified
lib/lp/services/webapp/authorization.py (+6/-1)
lib/lp/services/webhooks/model.py (+2/-3)
lib/lp/services/webhooks/tests/test_model.py (+8/-6)
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+384899@code.launchpad.net

Commit message

Fix webhooks for team-owned private targets

Description of the change

In order to cope with webhook targets that use DelegatedAuthorization-based security adapters, we had to switch to using iter_authorization rather than looking up the security adapter directly. However, this breaks for private webhook targets that are owned by a team, because teams don't have an account so we can't construct a normal principal for them. There's no appropriate principal here, because we need to know about things that are visible to the whole team in this case, not just to any particular member of the team.

However, iter_authorization only uses the principal to get hold of a corresponding IPersonRoles, and most security adapters work just fine on teams; so, to simplify all this and make it work in this case, we can just pass an IPersonRoles directly.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/services/webapp/authorization.py b/lib/lp/services/webapp/authorization.py
2index 5dc4ba5..aaaa55a 100644
3--- a/lib/lp/services/webapp/authorization.py
4+++ b/lib/lp/services/webapp/authorization.py
5@@ -1,4 +1,4 @@
6-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
7+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 __metaclass__ = type
11@@ -285,6 +285,11 @@ def iter_authorization(objecttoauthorize, permission, principal, cache,
12 if ILaunchpadPrincipal.providedBy(principal):
13 check_auth = lambda authorization: (
14 authorization.checkAuthenticated(IPersonRoles(principal.person)))
15+ elif IPersonRoles.providedBy(principal):
16+ # In some cases it's cumbersome to get hold of a proper principal,
17+ # so also allow passing an IPersonRoles directly.
18+ check_auth = lambda authorization: (
19+ authorization.checkAuthenticated(principal))
20 else:
21 check_auth = lambda authorization: (
22 authorization.checkUnauthenticated())
23diff --git a/lib/lp/services/webhooks/model.py b/lib/lp/services/webhooks/model.py
24index e60975f..a670d54 100644
25--- a/lib/lp/services/webhooks/model.py
26+++ b/lib/lp/services/webhooks/model.py
27@@ -42,6 +42,7 @@ from zope.interface import (
28 from zope.security.proxy import removeSecurityProxy
29
30 from lp.app import versioninfo
31+from lp.registry.interfaces.role import IPersonRoles
32 from lp.registry.model.person import Person
33 from lp.services.config import config
34 from lp.services.database.bulk import load_related
35@@ -61,7 +62,6 @@ from lp.services.job.model.job import (
36 from lp.services.job.runner import BaseRunnableJob
37 from lp.services.scripts import log
38 from lp.services.webapp.authorization import iter_authorization
39-from lp.services.webapp.interfaces import IPlacelessAuthUtility
40 from lp.services.webhooks.interfaces import (
41 IWebhook,
42 IWebhookClient,
43@@ -255,10 +255,9 @@ class WebhookSet:
44 :return: True if the context is visible to the webhook owner,
45 otherwise False.
46 """
47- authutil = getUtility(IPlacelessAuthUtility)
48 return all(iter_authorization(
49 removeSecurityProxy(context), "launchpad.View",
50- authutil.getPrincipal(user.accountID), {}))
51+ IPersonRoles(user), {}))
52
53 def trigger(self, target, event_type, payload, context=None):
54 if context is None:
55diff --git a/lib/lp/services/webhooks/tests/test_model.py b/lib/lp/services/webhooks/tests/test_model.py
56index 08b72fc..cc0c5a5 100644
57--- a/lib/lp/services/webhooks/tests/test_model.py
58+++ b/lib/lp/services/webhooks/tests/test_model.py
59@@ -206,7 +206,6 @@ class TestWebhookSetBase:
60
61 def test__checkVisibility_public_artifact(self):
62 target = self.makeTarget()
63- login_person(target.owner)
64 self.assertTrue(WebhookSet._checkVisibility(target, target.owner))
65
66 def test_trigger(self):
67@@ -252,7 +251,12 @@ class TestWebhookSetMergeProposalBase(TestWebhookSetBase):
68 owner = self.factory.makePerson()
69 target = self.makeTarget(
70 owner=owner, information_type=InformationType.PROPRIETARY)
71- login_person(owner)
72+ self.assertTrue(WebhookSet._checkVisibility(target, owner))
73+
74+ def test__checkVisibility_private_artifact_team_owned(self):
75+ owner = self.factory.makeTeam()
76+ target = self.makeTarget(
77+ owner=owner, information_type=InformationType.PROPRIETARY)
78 self.assertTrue(WebhookSet._checkVisibility(target, owner))
79
80 def test__checkVisibility_lost_access_to_private_artifact(self):
81@@ -269,9 +273,9 @@ class TestWebhookSetMergeProposalBase(TestWebhookSetBase):
82 policy=policy, grantee=grantee_team)
83 grantee_member = self.factory.makePerson(member_of=[grantee_team])
84 target = self.makeTarget(owner=grantee_member, project=project)
85- login_person(grantee_member)
86 self.assertTrue(WebhookSet._checkVisibility(target, grantee_member))
87- grantee_member.leave(grantee_team)
88+ with person_logged_in(grantee_member):
89+ grantee_member.leave(grantee_team)
90 self.assertFalse(WebhookSet._checkVisibility(target, grantee_member))
91
92 def test__checkVisibility_with_different_context(self):
93@@ -286,7 +290,6 @@ class TestWebhookSetMergeProposalBase(TestWebhookSetBase):
94 owner=owner, project=project, source=source, reviewer=reviewer)
95 mp2 = self.makeMergeProposal(
96 project=project, source=source, reviewer=reviewer)
97- login_person(owner)
98 self.assertTrue(
99 WebhookSet._checkVisibility(mp1, mp1.merge_target.owner))
100 self.assertFalse(
101@@ -343,7 +346,6 @@ class TestWebhookSetMergeProposalBase(TestWebhookSetBase):
102 event_type = 'merge-proposal:0.1'
103 hook = self.factory.makeWebhook(
104 target=target, event_types=[event_type])
105- login_person(source.owner)
106 getUtility(IWebhookSet).trigger(
107 target, event_type, {'some': 'payload'}, context=mp)
108 with admin_logged_in():

Subscribers

People subscribed via source and target branches

to status/vote changes: