Merge lp:~thumper/launchpad/trusted-reviewer into lp:launchpad
- trusted-reviewer
- Merge into devel
Proposed by
Tim Penhey
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~thumper/launchpad/trusted-reviewer |
Merge into: | lp:launchpad |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~thumper/launchpad/trusted-reviewer |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle | Approve | ||
Review via email: mp+9737@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote : | # |
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : | # |
Hi, looks basically fine.
Two questions:
1) is it really sane for admins to be able to review anything officially?
2) can you add a test of isPersonTrusted
Cheers,
mwh
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/code/browser/branchmergeproposal.py' |
2 | --- lib/lp/code/browser/branchmergeproposal.py 2009-08-05 02:18:13 +0000 |
3 | +++ lib/lp/code/browser/branchmergeproposal.py 2009-08-06 03:33:44 +0000 |
4 | @@ -433,13 +433,11 @@ |
5 | |
6 | def __init__(self, context, user, users_vote): |
7 | self.context = context |
8 | - is_mergable = self.context.branch_merge_proposal.isMergable() |
9 | + proposal = self.context.branch_merge_proposal |
10 | + is_mergable = proposal.isMergable() |
11 | self.can_change_review = (user == context.reviewer) and is_mergable |
12 | - branch = context.branch_merge_proposal.source_branch |
13 | - review_team = context.branch_merge_proposal.target_branch.reviewer |
14 | - reviewer = context.reviewer |
15 | - self.trusted = (reviewer is not None and reviewer.inTeam( |
16 | - review_team)) |
17 | + self.trusted = proposal.target_branch.isPersonTrustedReviewer( |
18 | + context.reviewer) |
19 | if user is None: |
20 | self.user_can_review = False |
21 | else: |
22 | @@ -802,7 +800,7 @@ |
23 | def initial_values(self): |
24 | # If the user is a valid reviewer, then default the revision |
25 | # number to be the tip. |
26 | - if self.context.isPersonValidReviewer(self.user): |
27 | + if self.context.target_branch.isPersonTrustedReviewer(self.user): |
28 | revision_number = self.context.source_branch.revision_count |
29 | else: |
30 | revision_number = self._getRevisionNumberForRevisionId( |
31 | @@ -820,14 +818,14 @@ |
32 | # If the user is not a valid reviewer for the target branch, |
33 | # then the revision number should be read only, so an |
34 | # untrusted user cannot land changes that have not bee reviewed. |
35 | - if not self.context.isPersonValidReviewer(self.user): |
36 | + if not self.context.target_branch.isPersonTrustedReviewer(self.user): |
37 | self.form_fields['revision_number'].for_display = True |
38 | |
39 | @action('Enqueue', name='enqueue') |
40 | @update_and_notify |
41 | def enqueue_action(self, action, data): |
42 | """Update the whiteboard and enqueue the merge proposal.""" |
43 | - if self.context.isPersonValidReviewer(self.user): |
44 | + if self.context.target_branch.isPersonTrustedReviewer(self.user): |
45 | revision_id = self._getRevisionId(data) |
46 | else: |
47 | revision_id = self.context.reviewed_revision_id |
48 | |
49 | === modified file 'lib/lp/code/configure.zcml' |
50 | --- lib/lp/code/configure.zcml 2009-07-30 00:30:02 +0000 |
51 | +++ lib/lp/code/configure.zcml 2009-08-06 03:33:44 +0000 |
52 | @@ -181,7 +181,6 @@ |
53 | votes |
54 | root_comment |
55 | all_comments |
56 | - isPersonValidReviewer |
57 | isMergable |
58 | getComment |
59 | getNotificationRecipients |
60 | @@ -348,6 +347,7 @@ |
61 | author |
62 | reviewer |
63 | code_reviewer |
64 | + isPersonTrustedReviewer |
65 | product |
66 | unique_name |
67 | displayname |
68 | |
69 | === modified file 'lib/lp/code/interfaces/branch.py' |
70 | --- lib/lp/code/interfaces/branch.py 2009-08-05 04:02:58 +0000 |
71 | +++ lib/lp/code/interfaces/branch.py 2009-08-06 03:33:44 +0000 |
72 | @@ -482,6 +482,19 @@ |
73 | code_reviewer = Attribute( |
74 | "The reviewer if set, otherwise the owner of the branch.") |
75 | |
76 | + @operation_parameters( |
77 | + reviewer=Reference( |
78 | + title=_("A person for which the reviewer status is in question."), |
79 | + schema=IPerson)) |
80 | + @export_read_operation() |
81 | + def isPersonTrustedReviewer(reviewer): |
82 | + """Return true if the `reviewer` is a trusted reviewer. |
83 | + |
84 | + The reviewer is trusted if they are either own the branch, or are in |
85 | + the team that owns the branch, or they are in the review team for the |
86 | + branch. |
87 | + """ |
88 | + |
89 | namespace = Attribute( |
90 | "The namespace of this branch, as an `IBranchNamespace`.") |
91 | |
92 | |
93 | === modified file 'lib/lp/code/interfaces/branchmergeproposal.py' |
94 | --- lib/lp/code/interfaces/branchmergeproposal.py 2009-08-04 05:02:41 +0000 |
95 | +++ lib/lp/code/interfaces/branchmergeproposal.py 2009-08-06 00:15:48 +0000 |
96 | @@ -395,21 +395,6 @@ |
97 | user-entered data like the whiteboard. |
98 | """ |
99 | |
100 | - @operation_parameters( |
101 | - reviewer=Reference( |
102 | - title=_("A person for which the reviewer status is in question."), |
103 | - schema=IPerson)) |
104 | - @export_read_operation() |
105 | - def isPersonValidReviewer(reviewer): |
106 | - """Return true if the `reviewer` is able to review the proposal. |
107 | - |
108 | - There is an attribute on branches called `reviewer` which allows |
109 | - a specific person or team to be set for a branch as an authorised |
110 | - person to approve merges for a branch. If a reviewer is not set |
111 | - on the target branch, then the owner of the target branch is used |
112 | - as the authorised user. |
113 | - """ |
114 | - |
115 | def isMergable(): |
116 | """Is the proposal in a state that allows it to being merged? |
117 | |
118 | |
119 | === modified file 'lib/lp/code/model/branch.py' |
120 | --- lib/lp/code/model/branch.py 2009-07-31 01:07:04 +0000 |
121 | +++ lib/lp/code/model/branch.py 2009-08-06 03:33:44 +0000 |
122 | @@ -36,6 +36,7 @@ |
123 | |
124 | from canonical.launchpad import _ |
125 | from lp.services.job.model.job import Job |
126 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
127 | from canonical.launchpad.mailnotification import NotificationRecipientSet |
128 | from canonical.launchpad.webapp import urlappend |
129 | from canonical.launchpad.webapp.interfaces import ( |
130 | @@ -440,6 +441,20 @@ |
131 | else: |
132 | return self.owner |
133 | |
134 | + def isPersonTrustedReviewer(self, reviewer): |
135 | + """See `IBranch`.""" |
136 | + if reviewer is None: |
137 | + return False |
138 | + # We trust Launchpad admins. |
139 | + lp_admins = getUtility(ILaunchpadCelebrities).admin |
140 | + # Both the branch owner and the review team are checked. |
141 | + owner = self.owner |
142 | + review_team = self.code_reviewer |
143 | + return ( |
144 | + reviewer.inTeam(owner) or |
145 | + reviewer.inTeam(review_team) or |
146 | + reviewer.inTeam(lp_admins)) |
147 | + |
148 | def latest_revisions(self, quantity=10): |
149 | """See `IBranch`.""" |
150 | return self.revision_history.limit(quantity) |
151 | |
152 | === modified file 'lib/lp/code/model/branchmergeproposal.py' |
153 | --- lib/lp/code/model/branchmergeproposal.py 2009-07-23 18:08:35 +0000 |
154 | +++ lib/lp/code/model/branchmergeproposal.py 2009-08-06 00:15:48 +0000 |
155 | @@ -47,7 +47,6 @@ |
156 | IBranchMergeProposal, IBranchMergeProposalGetter, UserNotBranchReviewer, |
157 | WrongBranchMergeProposal) |
158 | from lp.code.interfaces.branchtarget import IHasBranchTarget |
159 | -from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
160 | from lp.registry.interfaces.person import IPerson |
161 | from lp.registry.interfaces.product import IProduct |
162 | from lp.code.mail.branch import RecipientReason |
163 | @@ -78,7 +77,7 @@ |
164 | # Transitioning to code approved, rejected or queued from |
165 | # work in progress, needs review or merge failed needs the |
166 | # user to be a valid reviewer, other states are fine. |
167 | - valid_reviewer = proposal.isPersonValidReviewer(user) |
168 | + valid_reviewer = proposal.target_branch.isPersonTrustedReviewer(user) |
169 | if (next_state == rejected and not valid_reviewer): |
170 | return False |
171 | # Non-reviewers can toggle between code_approved and queued, but not |
172 | @@ -329,15 +328,6 @@ |
173 | self._transitionToState(BranchMergeProposalStatus.NEEDS_REVIEW) |
174 | self.date_review_requested = UTC_NOW |
175 | |
176 | - def isPersonValidReviewer(self, reviewer): |
177 | - """See `IBranchMergeProposal`.""" |
178 | - if reviewer is None: |
179 | - return False |
180 | - # We trust Launchpad admins. |
181 | - lp_admins = getUtility(ILaunchpadCelebrities).admin |
182 | - return (reviewer.inTeam(self.target_branch.code_reviewer) or |
183 | - reviewer.inTeam(lp_admins)) |
184 | - |
185 | def isMergable(self): |
186 | """See `IBranchMergeProposal`.""" |
187 | # As long as the source branch has not been merged, rejected |
188 | @@ -348,7 +338,7 @@ |
189 | """Set the proposal to one of the two review statuses.""" |
190 | # Check the reviewer can review the code for the target branch. |
191 | old_state = self.queue_status |
192 | - if not self.isPersonValidReviewer(reviewer): |
193 | + if not self.target_branch.isPersonTrustedReviewer(reviewer): |
194 | raise UserNotBranchReviewer |
195 | # Check the current state of the proposal. |
196 | self._transitionToState(next_state, reviewer) |
197 | |
198 | === modified file 'lib/lp/code/model/tests/test_branch.py' |
199 | --- lib/lp/code/model/tests/test_branch.py 2009-08-05 02:04:06 +0000 |
200 | +++ lib/lp/code/model/tests/test_branch.py 2009-08-06 03:33:44 +0000 |
201 | @@ -1651,6 +1651,73 @@ |
202 | self.assertEqual(branch.spec_links.count(), 0) |
203 | |
204 | |
205 | +class TestBranchIsPersonTrustedReviewer(TestCaseWithFactory): |
206 | + """Test the `IBranch.isPersonTrustedReviewer` method.""" |
207 | + |
208 | + layer = DatabaseFunctionalLayer |
209 | + |
210 | + def assertTrustedReviewer(self, branch, person): |
211 | + """Assert that `person` is a trusted reviewer for the `branch`.""" |
212 | + self.assertTrue(branch.isPersonTrustedReviewer(person)) |
213 | + |
214 | + def assertNotTrustedReviewer(self, branch, person): |
215 | + """Assert that `person` is not a trusted reviewer for the `branch`.""" |
216 | + self.assertFalse(branch.isPersonTrustedReviewer(person)) |
217 | + |
218 | + def test_none_is_not_trusted(self): |
219 | + # If None is passed in as the person, the method returns false. |
220 | + branch = self.factory.makeAnyBranch() |
221 | + self.assertNotTrustedReviewer(branch, None) |
222 | + |
223 | + def test_branch_owner_is_trusted(self): |
224 | + # The branch owner is a trusted reviewer. |
225 | + branch = self.factory.makeAnyBranch() |
226 | + self.assertTrustedReviewer(branch, branch.owner) |
227 | + |
228 | + def test_non_branch_owner_is_not_trusted(self): |
229 | + # Someone other than the branch owner is not a trusted reviewer. |
230 | + branch = self.factory.makeAnyBranch() |
231 | + reviewer = self.factory.makePerson() |
232 | + self.assertNotTrustedReviewer(branch, reviewer) |
233 | + |
234 | + def test_lp_admins_always_trusted(self): |
235 | + # Launchpad admins are special, and as such, are trusted. |
236 | + branch = self.factory.makeAnyBranch() |
237 | + admins = getUtility(ILaunchpadCelebrities).admin |
238 | + # Grab a random admin, the teamowner is good enough here. |
239 | + self.assertTrustedReviewer(branch, admins.teamowner) |
240 | + |
241 | + def test_member_of_team_owned_branch(self): |
242 | + # If the branch is owned by a team, any team member is a trusted |
243 | + # reviewer. |
244 | + team = self.factory.makeTeam() |
245 | + branch = self.factory.makeAnyBranch(owner=team) |
246 | + self.assertTrustedReviewer(branch, team.teamowner) |
247 | + |
248 | + def test_review_team_member_is_trusted(self): |
249 | + # If the reviewer is a member of the review team, but not the owner |
250 | + # they are still trusted. |
251 | + team = self.factory.makeTeam() |
252 | + branch = self.factory.makeAnyBranch(reviewer=team) |
253 | + self.assertTrustedReviewer(branch, team.teamowner) |
254 | + |
255 | + def test_branch_owner_not_review_team_member_is_trusted(self): |
256 | + # If the owner of the branch is not in the review team, they are still |
257 | + # trusted. |
258 | + team = self.factory.makeTeam() |
259 | + branch = self.factory.makeAnyBranch(reviewer=team) |
260 | + self.assertFalse(branch.owner.inTeam(team)) |
261 | + self.assertTrustedReviewer(branch, branch.owner) |
262 | + |
263 | + def test_community_reviewer(self): |
264 | + # If the reviewer is not a member of the owner, or the review team, |
265 | + # they are not trusted reviewers. |
266 | + team = self.factory.makeTeam() |
267 | + branch = self.factory.makeAnyBranch(reviewer=team) |
268 | + reviewer = self.factory.makePerson() |
269 | + self.assertNotTrustedReviewer(branch, reviewer) |
270 | + |
271 | + |
272 | class TestBranchSetOwner(TestCaseWithFactory): |
273 | """Tests for IBranch.setOwner.""" |
274 | |
275 | |
276 | === modified file 'lib/lp/code/model/tests/test_branchmergeproposals.py' |
277 | --- lib/lp/code/model/tests/test_branchmergeproposals.py 2009-07-22 16:02:55 +0000 |
278 | +++ lib/lp/code/model/tests/test_branchmergeproposals.py 2009-08-06 00:15:48 +0000 |
279 | @@ -22,6 +22,7 @@ |
280 | from canonical.testing import ( |
281 | DatabaseFunctionalLayer, LaunchpadFunctionalLayer, LaunchpadZopelessLayer) |
282 | |
283 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
284 | from lp.code.model.branchmergeproposaljob import ( |
285 | BranchMergeProposalJob, BranchMergeProposalJobType, |
286 | CreateMergeProposalJob, MergeProposalCreatedJob) |
287 | @@ -32,7 +33,7 @@ |
288 | NewBranchMergeProposalEvent, NewCodeReviewCommentEvent, |
289 | ReviewerNominatedEvent) |
290 | from canonical.launchpad.ftests import ( |
291 | - ANONYMOUS, import_secret_test_key, login, logout, syncUpdate) |
292 | + ANONYMOUS, import_secret_test_key, login, syncUpdate) |
293 | from lp.code.enums import ( |
294 | BranchMergeProposalStatus, BranchSubscriptionNotificationLevel, |
295 | BranchType, CodeReviewNotificationLevel, CodeReviewVote) |
296 | @@ -395,36 +396,6 @@ |
297 | proposal.date_created, proposal.date_review_requested) |
298 | |
299 | |
300 | -class TestBranchMergeProposalCanReview(TestCase): |
301 | - """Test the different cases that makes a branch deletable or not.""" |
302 | - |
303 | - layer = DatabaseFunctionalLayer |
304 | - |
305 | - def setUp(self): |
306 | - login('test@canonical.com') |
307 | - |
308 | - factory = LaunchpadObjectFactory() |
309 | - self.source_branch = factory.makeProductBranch() |
310 | - self.target_branch = factory.makeProductBranch( |
311 | - product=self.source_branch.product) |
312 | - registrant = factory.makePerson() |
313 | - self.proposal = self.source_branch.addLandingTarget( |
314 | - registrant, self.target_branch) |
315 | - |
316 | - def tearDown(self): |
317 | - logout() |
318 | - |
319 | - def test_validReviewer(self): |
320 | - """A newly created branch can be deleted without any problems.""" |
321 | - self.assertEqual(self.proposal.isPersonValidReviewer(None), |
322 | - False, "No user cannot review code") |
323 | - # The owner of the target branch is a valid reviewer. |
324 | - self.assertEqual( |
325 | - self.proposal.isPersonValidReviewer( |
326 | - self.target_branch.owner), |
327 | - True, "No user cannot review code") |
328 | - |
329 | - |
330 | class TestBranchMergeProposalQueueing(TestCase): |
331 | """Test the enqueueing and dequeueing of merge proposals.""" |
332 | |
333 | |
334 | === modified file 'lib/lp/code/stories/webservice/xx-branchmergeproposal.txt' |
335 | --- lib/lp/code/stories/webservice/xx-branchmergeproposal.txt 2009-07-16 01:22:05 +0000 |
336 | +++ lib/lp/code/stories/webservice/xx-branchmergeproposal.txt 2009-08-06 00:15:48 +0000 |
337 | @@ -117,14 +117,7 @@ |
338 | |
339 | A review can be performed through the API. |
340 | |
341 | - >>> person = webservice.get('/~target').jsonBody() |
342 | - >>> is_valid = webservice.named_get( |
343 | - ... merge_proposal['self_link'], 'isPersonValidReviewer', |
344 | - ... reviewer=person['self_link']).jsonBody() |
345 | - >>> print is_valid |
346 | - True |
347 | - |
348 | -A review can be requested of the person 'target', since he's a valid reviewer. |
349 | +A review can be requested of the person 'target'. |
350 | |
351 | >>> reviewer_webservice = webservice_for_person( |
352 | ... target_owner, permission=OAuthPermission.WRITE_PUBLIC) |
353 | |
354 | === modified file 'lib/lp/testing/factory.py' |
355 | --- lib/lp/testing/factory.py 2009-08-03 21:46:09 +0000 |
356 | +++ lib/lp/testing/factory.py 2009-08-06 03:33:44 +0000 |
357 | @@ -574,7 +574,7 @@ |
358 | def makeBranch(self, branch_type=None, owner=None, |
359 | name=None, product=_DEFAULT, url=_DEFAULT, registrant=None, |
360 | private=False, stacked_on=None, sourcepackage=None, |
361 | - **optional_branch_args): |
362 | + reviewer=None, **optional_branch_args): |
363 | """Create and return a new, arbitrary Branch of the given type. |
364 | |
365 | Any parameters for `IBranchNamespace.createBranch` can be specified to |
366 | @@ -624,6 +624,8 @@ |
367 | removeSecurityProxy(branch).private = True |
368 | if stacked_on is not None: |
369 | removeSecurityProxy(branch).stacked_on = stacked_on |
370 | + if reviewer is not None: |
371 | + removeSecurityProxy(branch).reviewer = reviewer |
372 | return branch |
373 | |
374 | def makePackageBranch(self, sourcepackage=None, distroseries=None, |
= Summary =
The (Community) tag is incorrectly being shown on some reviews.
Normally when the branch is owned by a team and there is no review team
specified.
== Proposed fix ==
The browser view was looking at IBranch.reviewer instead of code_reviewer. Instead of just fixing this, I went to the source of dReviewer' to the IBranch
IBranch.
the problem and added a method 'isPersonTruste
interface. It was always intended that the trust is based on being the owner
of the branch OR in the review team. The current implementation only checked
the owner if the review team wasn't specified. This branch fixes this
deficiency.
The 'isPersonValidR eviewer' is removed from IBranchMergePro posal, and call dReviewer' on the target branch.
sites are updated to check 'isPersonTruste
== Implementation details ==
The 'makeBranch' factory method is updated to be able to specify the
Branch.reviewer attribute. This is done by removing the security proxy so the
call site doesn't need to worry about launchpad.Edit for the branch.
== Tests ==
TestBranchIsPer sonTrustedRevie wer