Merge ~ines-almeida/launchpad:project-tokens/interfaces into launchpad:master

Proposed by Ines Almeida
Status: Merged
Approved by: Ines Almeida
Approved revision: 7ec00bbe3504e59c70eab25c0d38eeb1914df982
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~ines-almeida/launchpad:project-tokens/interfaces
Merge into: launchpad:master
Prerequisite: ~ines-almeida/launchpad:project-tokens/update-models
Diff against target: 182 lines (+57/-11)
6 files modified
lib/lp/registry/browser/product.py (+6/-0)
lib/lp/registry/interfaces/product.py (+10/-1)
lib/lp/registry/model/product.py (+2/-0)
lib/lp/services/auth/tests/test_browser.py (+25/-10)
lib/lp/services/auth/tests/test_model.py (+7/-0)
lib/lp/services/webapp/tests/test_servers.py (+7/-0)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+451543@code.launchpad.net

Commit message

Add interfaces to allow adding Project scoped access tokens through the UI and API

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

Remarkably little code here, but that's probably a sign of good design elsewhere. :-) (Of course this can't land until its prerequisite has landed.)

review: Approve
Revision history for this message
Ines Almeida (ines-almeida) wrote :

The 2 refactoring MPs I opened earlier took a lot of the code away from these others :)

Addressed the comment, this should be ready to merge once I merge its prerequisite

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/registry/browser/product.py b/lib/lp/registry/browser/product.py
2index bc77dd9..c6dc562 100644
3--- a/lib/lp/registry/browser/product.py
4+++ b/lib/lp/registry/browser/product.py
5@@ -469,6 +469,11 @@ class ProductEditLinksMixin(StructuralSubscriptionMenuMixin):
6 return Link("+configure-blueprints", text, summary, icon="edit")
7
8 @enabled_with_permission("launchpad.Edit")
9+ def access_tokens(self):
10+ text = "Manage access tokens"
11+ return Link("+access-tokens", text, icon="edit")
12+
13+ @enabled_with_permission("launchpad.Edit")
14 def branding(self):
15 text = "Change branding"
16 return Link("+branding", text, icon="edit")
17@@ -546,6 +551,7 @@ class ProductActionNavigationMenu(NavigationMenu, ProductEditLinksMixin):
18 "sharing",
19 "search_oci_project",
20 "new_oci_project",
21+ "access_tokens",
22 "webhooks",
23 ]
24 add_subscribe_link(links)
25diff --git a/lib/lp/registry/interfaces/product.py b/lib/lp/registry/interfaces/product.py
26index 790f77d..af127f0 100644
27--- a/lib/lp/registry/interfaces/product.py
28+++ b/lib/lp/registry/interfaces/product.py
29@@ -117,6 +117,10 @@ from lp.registry.interfaces.role import (
30 IHasDrivers,
31 IHasOwner,
32 )
33+from lp.services.auth.interfaces import (
34+ IAccessTokenTarget,
35+ IAccessTokenTargetEdit,
36+)
37 from lp.services.fields import (
38 Description,
39 IconImageUpload,
40@@ -606,6 +610,7 @@ class IProductView(
41 IHasCodeImports,
42 IServiceUsage,
43 IHasGitRepositories,
44+ IAccessTokenTarget,
45 ):
46 """Public IProduct properties."""
47
48@@ -1099,7 +1104,11 @@ class IProductView(
49 """
50
51
52-class IProductEditRestricted(IOfficialBugTagTargetRestricted, IWebhookTarget):
53+class IProductEditRestricted(
54+ IOfficialBugTagTargetRestricted,
55+ IWebhookTarget,
56+ IAccessTokenTargetEdit,
57+):
58 """`IProduct` properties which require launchpad.Edit permission."""
59
60 @mutator_for(IProductView["bug_sharing_policy"])
61diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
62index 4319feb..1ebe91c 100644
63--- a/lib/lp/registry/model/product.py
64+++ b/lib/lp/registry/model/product.py
65@@ -151,6 +151,7 @@ from lp.registry.model.series import ACTIVE_STATUSES
66 from lp.registry.model.sharingpolicy import SharingPolicyMixin
67 from lp.registry.model.sourcepackagename import SourcePackageName
68 from lp.registry.model.teammembership import TeamParticipation
69+from lp.services.auth.model import AccessTokenTargetMixin
70 from lp.services.database import bulk
71 from lp.services.database.constants import UTC_NOW
72 from lp.services.database.decoratedresultset import DecoratedResultSet
73@@ -249,6 +250,7 @@ specification_policy_default = {
74 @implementer(IBugSummaryDimension, IHasCustomLanguageCodes, IProduct)
75 class Product(
76 StormBase,
77+ AccessTokenTargetMixin,
78 BugTargetBase,
79 HasDriversMixin,
80 OfficialBugTagTargetMixin,
81diff --git a/lib/lp/services/auth/tests/test_browser.py b/lib/lp/services/auth/tests/test_browser.py
82index a1aba38..76e1221 100644
83--- a/lib/lp/services/auth/tests/test_browser.py
84+++ b/lib/lp/services/auth/tests/test_browser.py
85@@ -74,9 +74,9 @@ class TestAccessTokenViewBase:
86 return view
87
88 def test_access_tokens_link(self):
89- target_url = canonical_url(self.target, rootsite="code")
90+ target_url = canonical_url(self.target, rootsite=self.rootsite)
91 expected_tokens_url = canonical_url(
92- self.target, view_name="+access-tokens", rootsite="code"
93+ self.target, view_name="+access-tokens", rootsite=self.rootsite
94 )
95 browser = self.getUserBrowser(target_url, user=self.owner)
96 tokens_link = browser.getLink("Manage access tokens")
97@@ -114,20 +114,14 @@ class TestAccessTokenViewBase:
98 def test_empty(self):
99 self.assertThat(
100 self.makeView("+access-tokens")(),
101- MatchesAll(
102- token_listing_constants,
103- soupmatchers.HTMLContains(token_listing_tag),
104- ),
105+ MatchesAll(*self.getPageContent(token_matchers=[])),
106 )
107
108 def test_existing_tokens(self):
109 token_matchers = self.makeTokensAndMatchers(10)
110 self.assertThat(
111 self.makeView("+access-tokens")(),
112- MatchesAll(
113- token_listing_constants,
114- soupmatchers.HTMLContains(token_listing_tag, *token_matchers),
115- ),
116+ MatchesAll(*self.getPageContent(token_matchers)),
117 )
118
119 def test_revoke(self):
120@@ -190,8 +184,29 @@ class TestAccessTokenViewBase:
121 class TestAccessTokenViewGitRepository(
122 TestAccessTokenViewBase, TestCaseWithFactory
123 ):
124+ rootsite = "code"
125+
126 def makeTarget(self):
127 return self.factory.makeGitRepository()
128
129 def getTraversalStack(self, obj):
130 return [obj.target, obj]
131+
132+ def getPageContent(self, token_matchers):
133+ return [
134+ token_listing_constants,
135+ soupmatchers.HTMLContains(token_listing_tag, *token_matchers),
136+ ]
137+
138+
139+class TestAccessTokenViewProject(TestAccessTokenViewBase, TestCaseWithFactory):
140+ rootsite = None
141+
142+ def makeTarget(self):
143+ return self.factory.makeProduct()
144+
145+ def getTraversalStack(self, obj):
146+ return [obj]
147+
148+ def getPageContent(self, token_matchers):
149+ return [soupmatchers.HTMLContains(token_listing_tag, *token_matchers)]
150diff --git a/lib/lp/services/auth/tests/test_model.py b/lib/lp/services/auth/tests/test_model.py
151index a02904a..80dda03 100644
152--- a/lib/lp/services/auth/tests/test_model.py
153+++ b/lib/lp/services/auth/tests/test_model.py
154@@ -819,3 +819,10 @@ class TestAccessTokenTargetGitRepository(
155 b"user.",
156 response.body,
157 )
158+
159+
160+class TestAccessTokenTargetProject(
161+ TestAccessTokenTargetBase, TestCaseWithFactory
162+):
163+ def makeTarget(self):
164+ return self.factory.makeProduct()
165diff --git a/lib/lp/services/webapp/tests/test_servers.py b/lib/lp/services/webapp/tests/test_servers.py
166index e777ac2..491fed7 100644
167--- a/lib/lp/services/webapp/tests/test_servers.py
168+++ b/lib/lp/services/webapp/tests/test_servers.py
169@@ -968,6 +968,13 @@ class TestWebServiceAccessTokensGitRepository(
170 return self.factory.makeGitRepository(owner=owner)
171
172
173+class TestWebServiceAccessTokensProject(
174+ TestWebServiceAccessTokensBase, TestCaseWithFactory
175+):
176+ def makeTarget(self, owner=None):
177+ return self.factory.makeProduct(owner=owner)
178+
179+
180 def test_suite():
181 suite = unittest.TestSuite()
182 suite.addTest(

Subscribers

People subscribed via source and target branches

to status/vote changes: