Merge lp:~cjwatson/launchpad/git-finish-sharing into lp:launchpad
- git-finish-sharing
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | no longer in the source branch. |
Merged at revision: | 17366 |
Proposed branch: | lp:~cjwatson/launchpad/git-finish-sharing |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/git-collection |
Diff against target: |
1160 lines (+429/-105) 8 files modified
lib/lp/code/model/gitrepository.py (+9/-4) lib/lp/code/model/hasgitrepositories.py (+3/-3) lib/lp/code/model/tests/test_gitrepository.py (+136/-1) lib/lp/registry/services/sharingservice.py (+44/-37) lib/lp/registry/services/tests/test_sharingservice.py (+163/-45) lib/lp/registry/tests/test_accesspolicy.py (+8/-1) lib/lp/registry/tests/test_sharingjob.py (+58/-13) lib/lp/security.py (+8/-1) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/git-finish-sharing |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+250663@code.launchpad.net |
Commit message
Use Git repository collections to implement visibility and sharing.
Description of the change
Use Git repository collections to implement visibility and sharing.
There are a few bits we can't do until there's some analogue of BranchSubscription, but otherwise this should be a reasonably complete privacy implementation now.
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
1 | === modified file 'lib/lp/code/model/gitrepository.py' |
2 | --- lib/lp/code/model/gitrepository.py 2015-02-26 14:25:26 +0000 |
3 | +++ lib/lp/code/model/gitrepository.py 2015-02-27 11:27:39 +0000 |
4 | @@ -26,6 +26,7 @@ |
5 | ) |
6 | from zope.component import getUtility |
7 | from zope.interface import implements |
8 | +from zope.security.proxy import removeSecurityProxy |
9 | |
10 | from lp.app.enums import ( |
11 | InformationType, |
12 | @@ -39,6 +40,7 @@ |
13 | GitDefaultConflict, |
14 | GitTargetError, |
15 | ) |
16 | +from lp.code.interfaces.gitcollection import IAllGitRepositories |
17 | from lp.code.interfaces.gitlookup import IGitLookup |
18 | from lp.code.interfaces.gitnamespace import ( |
19 | get_git_namespace, |
20 | @@ -291,9 +293,8 @@ |
21 | elif user.id in self._known_viewers: |
22 | return True |
23 | else: |
24 | - # XXX cjwatson 2015-02-06: Fill this in once IGitCollection is |
25 | - # in place. |
26 | - return False |
27 | + return not getUtility(IAllGitRepositories).withIds( |
28 | + self.id).visibleByUser(user).is_empty() |
29 | |
30 | def getAllowedInformationTypes(self, user): |
31 | """See `IGitRepository`.""" |
32 | @@ -359,7 +360,11 @@ |
33 | def getByPath(self, user, path): |
34 | """See `IGitRepositorySet`.""" |
35 | repository = getUtility(IGitLookup).getByPath(path) |
36 | - if repository is not None and repository.visibleByUser(user): |
37 | + if repository is None: |
38 | + return None |
39 | + # removeSecurityProxy is safe here since we're explicitly performing |
40 | + # a permission check. |
41 | + if removeSecurityProxy(repository).visibleByUser(user): |
42 | return repository |
43 | return None |
44 | |
45 | |
46 | === modified file 'lib/lp/code/model/hasgitrepositories.py' |
47 | --- lib/lp/code/model/hasgitrepositories.py 2015-02-10 01:03:48 +0000 |
48 | +++ lib/lp/code/model/hasgitrepositories.py 2015-02-27 11:27:39 +0000 |
49 | @@ -8,6 +8,7 @@ |
50 | |
51 | from zope.component import getUtility |
52 | |
53 | +from lp.code.interfaces.gitcollection import IGitCollection |
54 | from lp.code.interfaces.gitrepository import IGitRepositorySet |
55 | |
56 | |
57 | @@ -23,6 +24,5 @@ |
58 | |
59 | def getGitRepositories(self, visible_by_user=None, eager_load=False): |
60 | """See `IHasGitRepositories`.""" |
61 | - # XXX cjwatson 2015-02-06: Fill this in once IGitCollection is in |
62 | - # place. |
63 | - raise NotImplementedError |
64 | + collection = IGitCollection(self).visibleByUser(visible_by_user) |
65 | + return collection.getRepositories(eager_load=eager_load) |
66 | |
67 | === modified file 'lib/lp/code/model/tests/test_gitrepository.py' |
68 | --- lib/lp/code/model/tests/test_gitrepository.py 2015-02-26 11:43:12 +0000 |
69 | +++ lib/lp/code/model/tests/test_gitrepository.py 2015-02-27 11:27:39 +0000 |
70 | @@ -34,11 +34,21 @@ |
71 | IGitRepository, |
72 | IGitRepositorySet, |
73 | ) |
74 | -from lp.registry.enums import BranchSharingPolicy |
75 | +from lp.registry.enums import ( |
76 | + BranchSharingPolicy, |
77 | + PersonVisibility, |
78 | + TeamMembershipPolicy, |
79 | + ) |
80 | +from lp.registry.interfaces.accesspolicy import ( |
81 | + IAccessArtifactSource, |
82 | + IAccessPolicyArtifactSource, |
83 | + IAccessPolicySource, |
84 | + ) |
85 | from lp.registry.interfaces.persondistributionsourcepackage import ( |
86 | IPersonDistributionSourcePackageFactory, |
87 | ) |
88 | from lp.registry.interfaces.personproduct import IPersonProductFactory |
89 | +from lp.registry.tests.test_accesspolicy import get_policies_for_artifact |
90 | from lp.services.database.constants import UTC_NOW |
91 | from lp.services.webapp.authorization import check_permission |
92 | from lp.testing import ( |
93 | @@ -134,6 +144,15 @@ |
94 | self.repository_set.setDefaultRepository(project, repository) |
95 | self.assertGitIdentity(repository, project.name) |
96 | |
97 | + def test_git_identity_private_default_for_project(self): |
98 | + # Private repositories also have a short lp: URL. |
99 | + project = self.factory.makeProduct() |
100 | + repository = self.factory.makeGitRepository( |
101 | + target=project, information_type=InformationType.USERDATA) |
102 | + with admin_logged_in(): |
103 | + self.repository_set.setDefaultRepository(project, repository) |
104 | + self.assertGitIdentity(repository, project.name) |
105 | + |
106 | def test_git_identity_default_for_package(self): |
107 | # If a repository is the default for a package, then its Git |
108 | # identity uses the path to that package. |
109 | @@ -302,6 +321,57 @@ |
110 | self.assertEqual(namespace, repository.namespace) |
111 | |
112 | |
113 | +class TestGitRepositoryPrivacy(TestCaseWithFactory): |
114 | + """Tests for Git repository privacy.""" |
115 | + |
116 | + layer = DatabaseFunctionalLayer |
117 | + |
118 | + def setUp(self): |
119 | + # Use an admin user as we aren't checking edit permissions here. |
120 | + super(TestGitRepositoryPrivacy, self).setUp("admin@canonical.com") |
121 | + |
122 | + def test_personal_repositories_for_private_teams_are_private(self): |
123 | + team = self.factory.makeTeam( |
124 | + membership_policy=TeamMembershipPolicy.MODERATED, |
125 | + visibility=PersonVisibility.PRIVATE) |
126 | + repository = self.factory.makeGitRepository(owner=team, target=team) |
127 | + self.assertTrue(repository.private) |
128 | + self.assertEqual( |
129 | + InformationType.PROPRIETARY, repository.information_type) |
130 | + |
131 | + def test__reconcileAccess_for_project_repository(self): |
132 | + # _reconcileAccess uses a project policy for a project repository. |
133 | + repository = self.factory.makeGitRepository( |
134 | + information_type=InformationType.USERDATA) |
135 | + [artifact] = getUtility(IAccessArtifactSource).ensure([repository]) |
136 | + getUtility(IAccessPolicyArtifactSource).deleteByArtifact([artifact]) |
137 | + removeSecurityProxy(repository)._reconcileAccess() |
138 | + self.assertContentEqual( |
139 | + getUtility(IAccessPolicySource).find( |
140 | + [(repository.target, InformationType.USERDATA)]), |
141 | + get_policies_for_artifact(repository)) |
142 | + |
143 | + def test__reconcileAccess_for_package_repository(self): |
144 | + # Git repository privacy isn't yet supported for distributions, so |
145 | + # no AccessPolicyArtifact is created for a package repository. |
146 | + repository = self.factory.makeGitRepository( |
147 | + target=self.factory.makeDistributionSourcePackage(), |
148 | + information_type=InformationType.USERDATA) |
149 | + removeSecurityProxy(repository)._reconcileAccess() |
150 | + self.assertEqual([], get_policies_for_artifact(repository)) |
151 | + |
152 | + def test__reconcileAccess_for_personal_repository(self): |
153 | + # _reconcileAccess uses a person policy for a personal repository. |
154 | + team_owner = self.factory.makeTeam() |
155 | + repository = self.factory.makeGitRepository( |
156 | + owner=team_owner, target=team_owner, |
157 | + information_type=InformationType.USERDATA) |
158 | + removeSecurityProxy(repository)._reconcileAccess() |
159 | + self.assertContentEqual( |
160 | + getUtility(IAccessPolicySource).findByTeam([team_owner]), |
161 | + get_policies_for_artifact(repository)) |
162 | + |
163 | + |
164 | class TestGitRepositoryGetAllowedInformationTypes(TestCaseWithFactory): |
165 | """Test `IGitRepository.getAllowedInformationTypes`.""" |
166 | |
167 | @@ -354,6 +424,17 @@ |
168 | self.assertFalse( |
169 | check_permission("launchpad.Moderate", repository)) |
170 | |
171 | + def test_methods_smoketest(self): |
172 | + # Users with launchpad.Moderate can call transitionToInformationType. |
173 | + project = self.factory.makeProduct() |
174 | + repository = self.factory.makeGitRepository(target=project) |
175 | + with person_logged_in(project.owner): |
176 | + project.setBranchSharingPolicy(BranchSharingPolicy.PUBLIC) |
177 | + repository.transitionToInformationType( |
178 | + InformationType.PRIVATESECURITY, project.owner) |
179 | + self.assertEqual( |
180 | + InformationType.PRIVATESECURITY, repository.information_type) |
181 | + |
182 | def test_attribute_smoketest(self): |
183 | # Users with launchpad.Moderate can set attributes. |
184 | project = self.factory.makeProduct() |
185 | @@ -486,6 +567,47 @@ |
186 | repository.setTarget(target=owner, user=owner) |
187 | self.assertEqual(owner, repository.target) |
188 | |
189 | + def test_private_personal_forbidden_for_public_teams(self): |
190 | + # Only private teams can have private personal repositories. |
191 | + owner = self.factory.makeTeam() |
192 | + repository = self.factory.makeGitRepository( |
193 | + owner=owner, information_type=InformationType.USERDATA) |
194 | + with admin_logged_in(): |
195 | + self.assertRaises( |
196 | + GitTargetError, repository.setTarget, target=owner, user=owner) |
197 | + |
198 | + def test_private_personal_allowed_for_private_teams(self): |
199 | + # Only private teams can have private personal repositories. |
200 | + owner = self.factory.makeTeam(visibility=PersonVisibility.PRIVATE) |
201 | + with person_logged_in(owner): |
202 | + repository = self.factory.makeGitRepository( |
203 | + owner=owner, information_type=InformationType.USERDATA) |
204 | + repository.setTarget(target=owner, user=owner) |
205 | + self.assertEqual(owner, repository.target) |
206 | + |
207 | + def test_reconciles_access(self): |
208 | + # setTarget calls _reconcileAccess to make the sharing schema |
209 | + # match the new target. |
210 | + repository = self.factory.makeGitRepository( |
211 | + information_type=InformationType.USERDATA) |
212 | + new_project = self.factory.makeProduct() |
213 | + with admin_logged_in(): |
214 | + repository.setTarget(target=new_project, user=repository.owner) |
215 | + self.assertEqual( |
216 | + new_project, get_policies_for_artifact(repository)[0].pillar) |
217 | + |
218 | + def test_reconciles_access_personal(self): |
219 | + # setTarget calls _reconcileAccess to make the sharing schema |
220 | + # correct for a private personal repository. |
221 | + owner = self.factory.makeTeam(visibility=PersonVisibility.PRIVATE) |
222 | + with person_logged_in(owner): |
223 | + repository = self.factory.makeGitRepository( |
224 | + owner=owner, target=owner, |
225 | + information_type=InformationType.USERDATA) |
226 | + repository.setTarget(target=owner, user=owner) |
227 | + self.assertEqual( |
228 | + owner, get_policies_for_artifact(repository)[0].person) |
229 | + |
230 | def test_public_to_proprietary_only_project(self): |
231 | # A repository cannot be moved to a target where the sharing policy |
232 | # does not allow it. |
233 | @@ -525,6 +647,19 @@ |
234 | person = self.factory.makePerson() |
235 | self.assertIsNone(self.repository_set.getByPath(person, "nonexistent")) |
236 | |
237 | + def test_getByPath_inaccessible(self): |
238 | + # If the given user cannot view the matched repository, then |
239 | + # getByPath returns None. |
240 | + owner = self.factory.makePerson() |
241 | + repository = self.factory.makeGitRepository( |
242 | + owner=owner, information_type=InformationType.USERDATA) |
243 | + with person_logged_in(owner): |
244 | + path = repository.shortened_path |
245 | + self.assertEqual( |
246 | + repository, self.repository_set.getByPath(owner, path)) |
247 | + self.assertIsNone( |
248 | + self.repository_set.getByPath(self.factory.makePerson(), path)) |
249 | + |
250 | def test_setDefaultRepository_refuses_person(self): |
251 | # setDefaultRepository refuses if the target is a person. |
252 | person = self.factory.makePerson() |
253 | |
254 | === modified file 'lib/lp/registry/services/sharingservice.py' |
255 | --- lib/lp/registry/services/sharingservice.py 2015-02-16 13:01:34 +0000 |
256 | +++ lib/lp/registry/services/sharingservice.py 2015-02-27 11:27:39 +0000 |
257 | @@ -9,6 +9,7 @@ |
258 | ] |
259 | |
260 | from itertools import product |
261 | +from operator import attrgetter |
262 | |
263 | from lazr.restful.interfaces import IWebBrowserOriginatingRequest |
264 | from lazr.restful.utils import get_current_web_service_request |
265 | @@ -36,6 +37,7 @@ |
266 | from lp.bugs.interfaces.bugtask import IBugTaskSet |
267 | from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams |
268 | from lp.code.interfaces.branchcollection import IAllBranches |
269 | +from lp.code.interfaces.gitcollection import IAllGitRepositories |
270 | from lp.registry.enums import ( |
271 | BranchSharingPolicy, |
272 | BugSharingPolicy, |
273 | @@ -227,7 +229,11 @@ |
274 | branches = list(wanted_branches.getBranches()) |
275 | # Load the Git repositories. |
276 | gitrepositories = [] |
277 | - # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place. |
278 | + if gitrepository_ids: |
279 | + all_gitrepositories = getUtility(IAllGitRepositories) |
280 | + wanted_gitrepositories = all_gitrepositories.visibleByUser( |
281 | + user).withIds(*gitrepository_ids) |
282 | + gitrepositories = list(wanted_gitrepositories.getRepositories()) |
283 | specifications = [] |
284 | if specification_ids: |
285 | specifications = load(Specification, specification_ids) |
286 | @@ -319,48 +325,52 @@ |
287 | gitrepositories=None, specifications=None, |
288 | ignore_permissions=False): |
289 | """See `ISharingService`.""" |
290 | - bugs_by_id = {} |
291 | - branches_by_id = {} |
292 | - gitrepositories_by_id = {} |
293 | + bug_ids = [] |
294 | + branch_ids = [] |
295 | + gitrepository_ids = [] |
296 | for bug in bugs or []: |
297 | if (not ignore_permissions |
298 | and not check_permission('launchpad.View', bug)): |
299 | raise Unauthorized |
300 | - bugs_by_id[bug.id] = bug |
301 | + bug_ids.append(bug.id) |
302 | for branch in branches or []: |
303 | if (not ignore_permissions |
304 | and not check_permission('launchpad.View', branch)): |
305 | raise Unauthorized |
306 | - branches_by_id[branch.id] = branch |
307 | + branch_ids.append(branch.id) |
308 | for gitrepository in gitrepositories or []: |
309 | if (not ignore_permissions |
310 | and not check_permission('launchpad.View', gitrepository)): |
311 | raise Unauthorized |
312 | - gitrepositories_by_id[gitrepository.id] = gitrepository |
313 | + gitrepository_ids.append(gitrepository.id) |
314 | for spec in specifications or []: |
315 | if (not ignore_permissions |
316 | and not check_permission('launchpad.View', spec)): |
317 | raise Unauthorized |
318 | |
319 | # Load the bugs. |
320 | - visible_bug_ids = [] |
321 | - if bugs_by_id: |
322 | - param = BugTaskSearchParams( |
323 | - user=person, bug=any(*bugs_by_id.keys())) |
324 | + visible_bugs = [] |
325 | + if bug_ids: |
326 | + param = BugTaskSearchParams(user=person, bug=any(*bug_ids)) |
327 | visible_bug_ids = set(getUtility(IBugTaskSet).searchBugIds(param)) |
328 | - visible_bugs = [bugs_by_id[bug_id] for bug_id in visible_bug_ids] |
329 | + visible_bugs = [bug for bug in bugs if bug.id in visible_bug_ids] |
330 | |
331 | # Load the branches. |
332 | visible_branches = [] |
333 | - if branches_by_id: |
334 | + if branch_ids: |
335 | all_branches = getUtility(IAllBranches) |
336 | wanted_branches = all_branches.visibleByUser(person).withIds( |
337 | - *branches_by_id.keys()) |
338 | + *branch_ids) |
339 | visible_branches = list(wanted_branches.getBranches()) |
340 | |
341 | # Load the Git repositories. |
342 | visible_gitrepositories = [] |
343 | - # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place. |
344 | + if gitrepository_ids: |
345 | + all_gitrepositories = getUtility(IAllGitRepositories) |
346 | + wanted_gitrepositories = all_gitrepositories.visibleByUser( |
347 | + person).withIds(*gitrepository_ids) |
348 | + visible_gitrepositories = list( |
349 | + wanted_gitrepositories.getRepositories()) |
350 | |
351 | # Load the specifications. |
352 | visible_specs = [] |
353 | @@ -378,40 +388,37 @@ |
354 | def getInvisibleArtifacts(self, person, bugs=None, branches=None, |
355 | gitrepositories=None): |
356 | """See `ISharingService`.""" |
357 | - bugs_by_id = {} |
358 | - branches_by_id = {} |
359 | - gitrepositories_by_id = {} |
360 | - for bug in bugs or []: |
361 | - bugs_by_id[bug.id] = bug |
362 | - for branch in branches or []: |
363 | - branches_by_id[branch.id] = branch |
364 | - for gitrepository in gitrepositories or []: |
365 | - gitrepositories_by_id[gitrepository.id] = gitrepository |
366 | + bug_ids = list(map(attrgetter("id"), bugs or [])) |
367 | + branch_ids = list(map(attrgetter("id"), branches or [])) |
368 | + gitrepository_ids = list(map(attrgetter("id"), gitrepositories or [])) |
369 | |
370 | # Load the bugs. |
371 | - visible_bug_ids = set() |
372 | - if bugs_by_id: |
373 | - param = BugTaskSearchParams( |
374 | - user=person, bug=any(*bugs_by_id.keys())) |
375 | + invisible_bugs = [] |
376 | + if bug_ids: |
377 | + param = BugTaskSearchParams(user=person, bug=any(*bug_ids)) |
378 | visible_bug_ids = set(getUtility(IBugTaskSet).searchBugIds(param)) |
379 | - invisible_bug_ids = set(bugs_by_id.keys()).difference(visible_bug_ids) |
380 | - invisible_bugs = [bugs_by_id[bug_id] for bug_id in invisible_bug_ids] |
381 | + invisible_bugs = [ |
382 | + bug for bug in bugs if bug.id not in visible_bug_ids] |
383 | |
384 | # Load the branches. |
385 | invisible_branches = [] |
386 | - if branches_by_id: |
387 | + if branch_ids: |
388 | all_branches = getUtility(IAllBranches) |
389 | visible_branch_ids = all_branches.visibleByUser(person).withIds( |
390 | - *branches_by_id.keys()).getBranchIds() |
391 | - invisible_branch_ids = ( |
392 | - set(branches_by_id.keys()).difference(visible_branch_ids)) |
393 | + *branch_ids).getBranchIds() |
394 | invisible_branches = [ |
395 | - branches_by_id[branch_id] |
396 | - for branch_id in invisible_branch_ids] |
397 | + branch for branch in branches |
398 | + if branch.id not in visible_branch_ids] |
399 | |
400 | # Load the Git repositories. |
401 | invisible_gitrepositories = [] |
402 | - # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place. |
403 | + if gitrepository_ids: |
404 | + all_gitrepositories = getUtility(IAllGitRepositories) |
405 | + visible_gitrepository_ids = all_gitrepositories.visibleByUser( |
406 | + person).withIds(*gitrepository_ids).getRepositoryIds() |
407 | + invisible_gitrepositories = [ |
408 | + gitrepository for gitrepository in gitrepositories |
409 | + if gitrepository.id not in visible_gitrepository_ids] |
410 | |
411 | return invisible_bugs, invisible_branches, invisible_gitrepositories |
412 | |
413 | |
414 | === modified file 'lib/lp/registry/services/tests/test_sharingservice.py' |
415 | --- lib/lp/registry/services/tests/test_sharingservice.py 2015-02-16 13:01:34 +0000 |
416 | +++ lib/lp/registry/services/tests/test_sharingservice.py 2015-02-27 11:27:39 +0000 |
417 | @@ -23,6 +23,7 @@ |
418 | CodeReviewNotificationLevel, |
419 | ) |
420 | from lp.code.interfaces.branch import IBranch |
421 | +from lp.code.interfaces.gitrepository import IGitRepository |
422 | from lp.registry.enums import ( |
423 | BranchSharingPolicy, |
424 | BugSharingPolicy, |
425 | @@ -928,12 +929,14 @@ |
426 | [InformationType.USERDATA]) |
427 | |
428 | def _assert_revokeAccessGrants(self, pillar, bugs, branches, |
429 | - specifications): |
430 | + gitrepositories, specifications): |
431 | artifacts = [] |
432 | if bugs: |
433 | artifacts.extend(bugs) |
434 | if branches: |
435 | artifacts.extend(branches) |
436 | + if gitrepositories: |
437 | + artifacts.extend(gitrepositories) |
438 | if specifications: |
439 | artifacts.extend(specifications) |
440 | policy = self.factory.makeAccessPolicy(pillar=pillar, |
441 | @@ -961,6 +964,8 @@ |
442 | branch.subscribe(person, |
443 | BranchSubscriptionNotificationLevel.NOEMAIL, None, |
444 | CodeReviewNotificationLevel.NOEMAIL, pillar.owner) |
445 | + # XXX cjwatson 2015-02-05: subscribe to Git repositories when |
446 | + # implemented |
447 | for spec in specifications or []: |
448 | spec.subscribe(person) |
449 | |
450 | @@ -973,7 +978,7 @@ |
451 | |
452 | self.service.revokeAccessGrants( |
453 | pillar, grantee, pillar.owner, bugs=bugs, branches=branches, |
454 | - specifications=specifications) |
455 | + gitrepositories=gitrepositories, specifications=specifications) |
456 | with block_on_job(self): |
457 | transaction.commit() |
458 | |
459 | @@ -987,18 +992,22 @@ |
460 | self.assertNotIn(grantee, bug.getDirectSubscribers()) |
461 | for branch in branches or []: |
462 | self.assertNotIn(grantee, branch.subscribers) |
463 | + # XXX cjwatson 2015-02-05: check revocation of subscription to Git |
464 | + # repositories when implemented |
465 | for spec in specifications or []: |
466 | self.assertNotIn(grantee, spec.subscribers) |
467 | |
468 | - # Someone else still has access to the bugs and branches. |
469 | + # Someone else still has access to the artifacts. |
470 | grants = accessartifact_grant_source.findByArtifact( |
471 | access_artifacts, [someone]) |
472 | self.assertEqual(1, grants.count()) |
473 | - # Someone else still has subscriptions to the bugs and branches. |
474 | + # Someone else still has subscriptions to the artifacts. |
475 | for bug in bugs or []: |
476 | self.assertIn(someone, bug.getDirectSubscribers()) |
477 | for branch in branches or []: |
478 | self.assertIn(someone, branch.subscribers) |
479 | + # XXX cjwatson 2015-02-05: check subscription to Git repositories |
480 | + # when implemented |
481 | for spec in specifications or []: |
482 | self.assertIn(someone, spec.subscribers) |
483 | |
484 | @@ -1010,7 +1019,7 @@ |
485 | bug = self.factory.makeBug( |
486 | target=distro, owner=owner, |
487 | information_type=InformationType.USERDATA) |
488 | - self._assert_revokeAccessGrants(distro, [bug], None, None) |
489 | + self._assert_revokeAccessGrants(distro, [bug], None, None, None) |
490 | |
491 | def test_revokeAccessGrantsBranches(self): |
492 | owner = self.factory.makePerson() |
493 | @@ -1019,7 +1028,17 @@ |
494 | branch = self.factory.makeBranch( |
495 | product=product, owner=owner, |
496 | information_type=InformationType.USERDATA) |
497 | - self._assert_revokeAccessGrants(product, None, [branch], None) |
498 | + self._assert_revokeAccessGrants(product, None, [branch], None, None) |
499 | + |
500 | + def test_revokeAccessGrantsGitRepositories(self): |
501 | + owner = self.factory.makePerson() |
502 | + product = self.factory.makeProduct(owner=owner) |
503 | + login_person(owner) |
504 | + gitrepository = self.factory.makeGitRepository( |
505 | + target=product, owner=owner, |
506 | + information_type=InformationType.USERDATA) |
507 | + self._assert_revokeAccessGrants( |
508 | + product, None, None, [gitrepository], None) |
509 | |
510 | def test_revokeAccessGrantsSpecifications(self): |
511 | owner = self.factory.makePerson() |
512 | @@ -1030,15 +1049,18 @@ |
513 | specification = self.factory.makeSpecification( |
514 | product=product, owner=owner, |
515 | information_type=InformationType.EMBARGOED) |
516 | - self._assert_revokeAccessGrants(product, None, None, [specification]) |
517 | + self._assert_revokeAccessGrants( |
518 | + product, None, None, None, [specification]) |
519 | |
520 | def _assert_revokeTeamAccessGrants(self, pillar, bugs, branches, |
521 | - specifications): |
522 | + gitrepositories, specifications): |
523 | artifacts = [] |
524 | if bugs: |
525 | artifacts.extend(bugs) |
526 | if branches: |
527 | artifacts.extend(branches) |
528 | + if gitrepositories: |
529 | + artifacts.extend(gitrepositories) |
530 | if specifications: |
531 | artifacts.extend(specifications) |
532 | policy = self.factory.makeAccessPolicy(pillar=pillar, |
533 | @@ -1065,6 +1087,8 @@ |
534 | branch.subscribe( |
535 | person, BranchSubscriptionNotificationLevel.NOEMAIL, |
536 | None, CodeReviewNotificationLevel.NOEMAIL, pillar.owner) |
537 | + # XXX cjwatson 2015-02-05: subscribe to Git repositories when |
538 | + # implemented |
539 | # Subscribing somebody to a specification does not yet imply |
540 | # granting access to this person. |
541 | if specifications: |
542 | @@ -1075,12 +1099,16 @@ |
543 | |
544 | # Check that grantees have expected access grants and subscriptions. |
545 | for person in [team_grantee, person_grantee]: |
546 | - visible_bugs, visible_branches, _, visible_specs = ( |
547 | + (visible_bugs, visible_branches, visible_gitrepositories, |
548 | + visible_specs) = ( |
549 | self.service.getVisibleArtifacts( |
550 | person, bugs=bugs, branches=branches, |
551 | + gitrepositories=gitrepositories, |
552 | specifications=specifications)) |
553 | self.assertContentEqual(bugs or [], visible_bugs) |
554 | self.assertContentEqual(branches or [], visible_branches) |
555 | + # XXX cjwatson 2015-02-05: check Git repositories when |
556 | + # subscription is implemented |
557 | self.assertContentEqual(specifications or [], visible_specs) |
558 | for person in [team_grantee, person_grantee]: |
559 | for bug in bugs or []: |
560 | @@ -1088,7 +1116,7 @@ |
561 | |
562 | self.service.revokeAccessGrants( |
563 | pillar, team_grantee, pillar.owner, bugs=bugs, branches=branches, |
564 | - specifications=specifications) |
565 | + gitrepositories=gitrepositories, specifications=specifications) |
566 | with block_on_job(self): |
567 | transaction.commit() |
568 | |
569 | @@ -1103,11 +1131,14 @@ |
570 | for person in [team_grantee, person_grantee]: |
571 | for bug in bugs or []: |
572 | self.assertNotIn(person, bug.getDirectSubscribers()) |
573 | - visible_bugs, visible_branches, _, visible_specs = ( |
574 | + (visible_bugs, visible_branches, visible_gitrepositories, |
575 | + visible_specs) = ( |
576 | self.service.getVisibleArtifacts( |
577 | - person, bugs=bugs, branches=branches)) |
578 | + person, bugs=bugs, branches=branches, |
579 | + gitrepositories=gitrepositories)) |
580 | self.assertContentEqual([], visible_bugs) |
581 | self.assertContentEqual([], visible_branches) |
582 | + self.assertContentEqual([], visible_gitrepositories) |
583 | self.assertContentEqual([], visible_specs) |
584 | |
585 | def test_revokeTeamAccessGrantsBugs(self): |
586 | @@ -1118,7 +1149,7 @@ |
587 | bug = self.factory.makeBug( |
588 | target=distro, owner=owner, |
589 | information_type=InformationType.USERDATA) |
590 | - self._assert_revokeTeamAccessGrants(distro, [bug], None, None) |
591 | + self._assert_revokeTeamAccessGrants(distro, [bug], None, None, None) |
592 | |
593 | def test_revokeTeamAccessGrantsBranches(self): |
594 | # Users with launchpad.Edit can delete all access for a grantee. |
595 | @@ -1127,7 +1158,20 @@ |
596 | login_person(owner) |
597 | branch = self.factory.makeBranch( |
598 | owner=owner, information_type=InformationType.USERDATA) |
599 | - self._assert_revokeTeamAccessGrants(product, None, [branch], None) |
600 | + self._assert_revokeTeamAccessGrants( |
601 | + product, None, [branch], None, None) |
602 | + |
603 | + # XXX cjwatson 2015-02-05: Enable this once GitRepositorySubscription is |
604 | + # implemented. |
605 | + def disabled_test_revokeTeamAccessGrantsGitRepositories(self): |
606 | + # Users with launchpad.Edit can delete all access for a grantee. |
607 | + owner = self.factory.makePerson() |
608 | + product = self.factory.makeProduct(owner=owner) |
609 | + login_person(owner) |
610 | + gitrepository = self.factory.makeGitRepository( |
611 | + owner=owner, information_type=InformationType.USERDATA) |
612 | + self._assert_revokeTeamAccessGrants( |
613 | + product, None, None, [gitrepository], None) |
614 | |
615 | def test_revokeTeamAccessGrantsSpecifications(self): |
616 | # Users with launchpad.Edit can delete all access for a grantee. |
617 | @@ -1140,7 +1184,7 @@ |
618 | product=product, owner=owner, |
619 | information_type=InformationType.EMBARGOED) |
620 | self._assert_revokeTeamAccessGrants( |
621 | - product, None, None, [specification]) |
622 | + product, None, None, None, [specification]) |
623 | |
624 | def _assert_revokeAccessGrantsUnauthorized(self): |
625 | # revokeAccessGrants raises an Unauthorized exception if the user |
626 | @@ -1163,9 +1207,9 @@ |
627 | login_person(self.factory.makePerson()) |
628 | self._assert_revokeAccessGrantsUnauthorized() |
629 | |
630 | - def test_revokeAccessGrants_without_bugs_or_branches(self): |
631 | + def test_revokeAccessGrants_without_artifacts(self): |
632 | # The revokeAccessGrants method raises a ValueError if called without |
633 | - # specifying either bugs or branches. |
634 | + # specifying any artifacts. |
635 | owner = self.factory.makePerson() |
636 | product = self.factory.makeProduct(owner=owner) |
637 | grantee = self.factory.makePerson() |
638 | @@ -1174,24 +1218,27 @@ |
639 | ValueError, self.service.revokeAccessGrants, |
640 | product, grantee, product.owner) |
641 | |
642 | - def _assert_ensureAccessGrants(self, user, bugs, branches, specifications, |
643 | - grantee=None): |
644 | + def _assert_ensureAccessGrants(self, user, bugs, branches, gitrepositories, |
645 | + specifications, grantee=None): |
646 | # Creating access grants works as expected. |
647 | if not grantee: |
648 | grantee = self.factory.makePerson() |
649 | self.service.ensureAccessGrants( |
650 | [grantee], user, bugs=bugs, branches=branches, |
651 | - specifications=specifications) |
652 | + gitrepositories=gitrepositories, specifications=specifications) |
653 | |
654 | # Check that grantee has expected access grants. |
655 | shared_bugs = [] |
656 | shared_branches = [] |
657 | + shared_gitrepositories = [] |
658 | shared_specifications = [] |
659 | all_pillars = [] |
660 | for bug in bugs or []: |
661 | all_pillars.extend(bug.affected_pillars) |
662 | for branch in branches or []: |
663 | all_pillars.append(branch.target.context) |
664 | + for gitrepository in gitrepositories or []: |
665 | + all_pillars.append(gitrepository.target) |
666 | for specification in specifications or []: |
667 | all_pillars.append(specification.target) |
668 | policies = getUtility(IAccessPolicySource).findByPillar(all_pillars) |
669 | @@ -1203,10 +1250,13 @@ |
670 | shared_bugs.append(a.concrete_artifact) |
671 | elif IBranch.providedBy(a.concrete_artifact): |
672 | shared_branches.append(a.concrete_artifact) |
673 | + elif IGitRepository.providedBy(a.concrete_artifact): |
674 | + shared_gitrepositories.append(a.concrete_artifact) |
675 | elif ISpecification.providedBy(a.concrete_artifact): |
676 | shared_specifications.append(a.concrete_artifact) |
677 | self.assertContentEqual(bugs or [], shared_bugs) |
678 | self.assertContentEqual(branches or [], shared_branches) |
679 | + self.assertContentEqual(gitrepositories or [], shared_gitrepositories) |
680 | self.assertContentEqual(specifications or [], shared_specifications) |
681 | |
682 | def test_ensureAccessGrantsBugs(self): |
683 | @@ -1217,7 +1267,7 @@ |
684 | bug = self.factory.makeBug( |
685 | target=distro, owner=owner, |
686 | information_type=InformationType.USERDATA) |
687 | - self._assert_ensureAccessGrants(owner, [bug], None, None) |
688 | + self._assert_ensureAccessGrants(owner, [bug], None, None, None) |
689 | |
690 | def test_ensureAccessGrantsBranches(self): |
691 | # Access grants can be created for branches. |
692 | @@ -1227,7 +1277,18 @@ |
693 | branch = self.factory.makeBranch( |
694 | product=product, owner=owner, |
695 | information_type=InformationType.USERDATA) |
696 | - self._assert_ensureAccessGrants(owner, None, [branch], None) |
697 | + self._assert_ensureAccessGrants(owner, None, [branch], None, None) |
698 | + |
699 | + def test_ensureAccessGrantsGitRepositories(self): |
700 | + # Access grants can be created for Git repositories. |
701 | + owner = self.factory.makePerson() |
702 | + product = self.factory.makeProduct(owner=owner) |
703 | + login_person(owner) |
704 | + gitrepository = self.factory.makeGitRepository( |
705 | + target=product, owner=owner, |
706 | + information_type=InformationType.USERDATA) |
707 | + self._assert_ensureAccessGrants( |
708 | + owner, None, None, [gitrepository], None) |
709 | |
710 | def test_ensureAccessGrantsSpecifications(self): |
711 | # Access grants can be created for branches. |
712 | @@ -1244,7 +1305,8 @@ |
713 | with person_logged_in(owner): |
714 | specification.transitionToInformationType( |
715 | InformationType.PROPRIETARY, owner) |
716 | - self._assert_ensureAccessGrants(owner, None, None, [specification]) |
717 | + self._assert_ensureAccessGrants( |
718 | + owner, None, None, None, [specification]) |
719 | |
720 | def test_ensureAccessGrantsExisting(self): |
721 | # Any existing access grants are retained and new ones created. |
722 | @@ -1263,7 +1325,7 @@ |
723 | # Test with a new bug as well as the one for which access is already |
724 | # granted. |
725 | self._assert_ensureAccessGrants( |
726 | - owner, [bug, bug2], None, None, grantee) |
727 | + owner, [bug, bug2], None, None, None, grantee) |
728 | |
729 | def _assert_ensureAccessGrantsUnauthorized(self, user): |
730 | # ensureAccessGrants raises an Unauthorized exception if the user |
731 | @@ -1331,7 +1393,8 @@ |
732 | self._assert_updatePillarSharingPoliciesUnauthorized(anyone) |
733 | |
734 | def create_shared_artifacts(self, product, grantee, user): |
735 | - # Create some shared bugs and branches. |
736 | + # Create some shared bugs, branches, Git repositories, and |
737 | + # specifications. |
738 | bugs = [] |
739 | bug_tasks = [] |
740 | for x in range(0, 10): |
741 | @@ -1346,6 +1409,12 @@ |
742 | product=product, owner=product.owner, |
743 | information_type=InformationType.USERDATA) |
744 | branches.append(branch) |
745 | + gitrepositories = [] |
746 | + for x in range(0, 10): |
747 | + gitrepository = self.factory.makeGitRepository( |
748 | + target=product, owner=product.owner, |
749 | + information_type=InformationType.USERDATA) |
750 | + gitrepositories.append(gitrepository) |
751 | specs = [] |
752 | for x in range(0, 10): |
753 | spec = self.factory.makeSpecification( |
754 | @@ -1371,9 +1440,11 @@ |
755 | grant_access(bug, i == 9) |
756 | for i, branch in enumerate(branches): |
757 | grant_access(branch, i == 9) |
758 | + for i, gitrepository in enumerate(gitrepositories): |
759 | + grant_access(gitrepository, i == 9) |
760 | getUtility(IService, 'sharing').ensureAccessGrants( |
761 | [grantee], product.owner, specifications=specs[:9]) |
762 | - return bug_tasks, branches, specs |
763 | + return bug_tasks, branches, gitrepositories, specs |
764 | |
765 | def test_getSharedArtifacts(self): |
766 | # Test the getSharedArtifacts method. |
767 | @@ -1384,14 +1455,16 @@ |
768 | login_person(owner) |
769 | grantee = self.factory.makePerson() |
770 | user = self.factory.makePerson() |
771 | - bug_tasks, branches, specs = self.create_shared_artifacts( |
772 | - product, grantee, user) |
773 | + bug_tasks, branches, gitrepositories, specs = ( |
774 | + self.create_shared_artifacts(product, grantee, user)) |
775 | |
776 | # Check the results. |
777 | - shared_bugtasks, shared_branches, _, shared_specs = ( |
778 | + (shared_bugtasks, shared_branches, shared_gitrepositories, |
779 | + shared_specs) = ( |
780 | self.service.getSharedArtifacts(product, grantee, user)) |
781 | self.assertContentEqual(bug_tasks[:9], shared_bugtasks) |
782 | self.assertContentEqual(branches[:9], shared_branches) |
783 | + self.assertContentEqual(gitrepositories[:9], shared_gitrepositories) |
784 | self.assertContentEqual(specs[:9], shared_specs) |
785 | |
786 | def _assert_getSharedProjects(self, product, who=None): |
787 | @@ -1531,7 +1604,7 @@ |
788 | login_person(owner) |
789 | grantee = self.factory.makePerson() |
790 | user = self.factory.makePerson() |
791 | - bug_tasks, ignored, ignored = self.create_shared_artifacts( |
792 | + bug_tasks, _, _, _ = self.create_shared_artifacts( |
793 | product, grantee, user) |
794 | |
795 | # Check the results. |
796 | @@ -1547,7 +1620,7 @@ |
797 | login_person(owner) |
798 | grantee = self.factory.makePerson() |
799 | user = self.factory.makePerson() |
800 | - ignored, branches, ignored = self.create_shared_artifacts( |
801 | + _, branches, _, _ = self.create_shared_artifacts( |
802 | product, grantee, user) |
803 | |
804 | # Check the results. |
805 | @@ -1555,6 +1628,23 @@ |
806 | product, grantee, user) |
807 | self.assertContentEqual(branches[:9], shared_branches) |
808 | |
809 | + def test_getSharedGitRepositories(self): |
810 | + # Test the getSharedGitRepositories method. |
811 | + owner = self.factory.makePerson() |
812 | + product = self.factory.makeProduct( |
813 | + owner=owner, specification_sharing_policy=( |
814 | + SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY)) |
815 | + login_person(owner) |
816 | + grantee = self.factory.makePerson() |
817 | + user = self.factory.makePerson() |
818 | + _, _, gitrepositories, _ = self.create_shared_artifacts( |
819 | + product, grantee, user) |
820 | + |
821 | + # Check the results. |
822 | + shared_gitrepositories = self.service.getSharedGitRepositories( |
823 | + product, grantee, user) |
824 | + self.assertContentEqual(gitrepositories[:9], shared_gitrepositories) |
825 | + |
826 | def test_getSharedSpecifications(self): |
827 | # Test the getSharedSpecifications method. |
828 | owner = self.factory.makePerson() |
829 | @@ -1564,7 +1654,7 @@ |
830 | login_person(owner) |
831 | grantee = self.factory.makePerson() |
832 | user = self.factory.makePerson() |
833 | - ignored, ignored, specifications = self.create_shared_artifacts( |
834 | + _, _, _, specifications = self.create_shared_artifacts( |
835 | product, grantee, user) |
836 | |
837 | # Check the results. |
838 | @@ -1592,6 +1682,16 @@ |
839 | login_person(owner) |
840 | self._assert_getPeopleWithoutAccess(product, branch) |
841 | |
842 | + def test_getPeopleWithAccessGitRepositories(self): |
843 | + # Test the getPeopleWithoutAccess method with Git repositories. |
844 | + owner = self.factory.makePerson() |
845 | + product = self.factory.makeProduct(owner=owner) |
846 | + gitrepository = self.factory.makeGitRepository( |
847 | + target=product, owner=owner, |
848 | + information_type=InformationType.USERDATA) |
849 | + login_person(owner) |
850 | + self._assert_getPeopleWithoutAccess(product, gitrepository) |
851 | + |
852 | def _assert_getPeopleWithoutAccess(self, product, artifact): |
853 | access_artifact = self.factory.makeAccessArtifact(concrete=artifact) |
854 | # Make some people to check. people[:5] will not have access. |
855 | @@ -1647,6 +1747,12 @@ |
856 | product=product, owner=owner, |
857 | information_type=InformationType.USERDATA) |
858 | branches.append(branch) |
859 | + gitrepositories = [] |
860 | + for x in range(0, 10): |
861 | + gitrepository = self.factory.makeGitRepository( |
862 | + target=product, owner=owner, |
863 | + information_type=InformationType.USERDATA) |
864 | + gitrepositories.append(gitrepository) |
865 | |
866 | specifications = [] |
867 | for x in range(0, 10): |
868 | @@ -1662,46 +1768,58 @@ |
869 | artifact=access_artifact, grantee=grantee, grantor=owner) |
870 | return access_artifact |
871 | |
872 | - # Grant access to some of the bugs and branches. |
873 | + # Grant access to some of the artifacts. |
874 | for bug in bugs[:5]: |
875 | grant_access(bug) |
876 | for branch in branches[:5]: |
877 | grant_access(branch) |
878 | + for gitrepository in gitrepositories[:5]: |
879 | + grant_access(gitrepository) |
880 | for spec in specifications[:5]: |
881 | grant_access(spec) |
882 | - return grantee, owner, branches, bugs, specifications |
883 | + return grantee, owner, bugs, branches, gitrepositories, specifications |
884 | |
885 | def test_getVisibleArtifacts(self): |
886 | # Test the getVisibleArtifacts method. |
887 | - grantee, ignore, branches, bugs, specs = self._make_Artifacts() |
888 | + grantee, ignore, bugs, branches, gitrepositories, specs = ( |
889 | + self._make_Artifacts()) |
890 | # Check the results. |
891 | - shared_bugs, shared_branches, _, shared_specs = ( |
892 | + shared_bugs, shared_branches, shared_gitrepositories, shared_specs = ( |
893 | self.service.getVisibleArtifacts( |
894 | - grantee, bugs=bugs, branches=branches, specifications=specs)) |
895 | + grantee, bugs=bugs, branches=branches, |
896 | + gitrepositories=gitrepositories, specifications=specs)) |
897 | self.assertContentEqual(bugs[:5], shared_bugs) |
898 | self.assertContentEqual(branches[:5], shared_branches) |
899 | + self.assertContentEqual(gitrepositories[:5], shared_gitrepositories) |
900 | self.assertContentEqual(specs[:5], shared_specs) |
901 | |
902 | def test_getVisibleArtifacts_grant_on_pillar(self): |
903 | # getVisibleArtifacts() returns private specifications if |
904 | # user has a policy grant for the pillar of the specification. |
905 | - ignore, owner, branches, bugs, specs = self._make_Artifacts() |
906 | - shared_bugs, shared_branches, _, shared_specs = ( |
907 | + _, owner, bugs, branches, gitrepositories, specs = ( |
908 | + self._make_Artifacts()) |
909 | + shared_bugs, shared_branches, shared_gitrepositories, shared_specs = ( |
910 | self.service.getVisibleArtifacts( |
911 | - owner, bugs=bugs, branches=branches, specifications=specs)) |
912 | + owner, bugs=bugs, branches=branches, |
913 | + gitrepositories=gitrepositories, specifications=specs)) |
914 | self.assertContentEqual(bugs, shared_bugs) |
915 | self.assertContentEqual(branches, shared_branches) |
916 | + self.assertContentEqual(gitrepositories, shared_gitrepositories) |
917 | self.assertContentEqual(specs, shared_specs) |
918 | |
919 | def test_getInvisibleArtifacts(self): |
920 | # Test the getInvisibleArtifacts method. |
921 | - grantee, ignore, branches, bugs, specs = self._make_Artifacts() |
922 | + grantee, ignore, bugs, branches, gitrepositories, specs = ( |
923 | + self._make_Artifacts()) |
924 | # Check the results. |
925 | - not_shared_bugs, not_shared_branches, _ = ( |
926 | + not_shared_bugs, not_shared_branches, not_shared_gitrepositories = ( |
927 | self.service.getInvisibleArtifacts( |
928 | - grantee, bugs=bugs, branches=branches)) |
929 | + grantee, bugs=bugs, branches=branches, |
930 | + gitrepositories=gitrepositories)) |
931 | self.assertContentEqual(bugs[5:], not_shared_bugs) |
932 | self.assertContentEqual(branches[5:], not_shared_branches) |
933 | + self.assertContentEqual( |
934 | + gitrepositories[5:], not_shared_gitrepositories) |
935 | |
936 | def _assert_getVisibleArtifacts_bug_change(self, change_callback): |
937 | # Test the getVisibleArtifacts method excludes bugs after a change of |
938 | @@ -1723,7 +1841,7 @@ |
939 | information_type=InformationType.USERDATA) |
940 | bugs.append(bug) |
941 | |
942 | - shared_bugs, shared_branches, _, shared_specs = ( |
943 | + shared_bugs, shared_branches, shared_gitrepositories, shared_specs = ( |
944 | self.service.getVisibleArtifacts(grantee, bugs=bugs)) |
945 | self.assertContentEqual(bugs, shared_bugs) |
946 | |
947 | @@ -1731,7 +1849,7 @@ |
948 | for x in range(0, 5): |
949 | change_callback(bugs[x], owner) |
950 | # Check the results. |
951 | - shared_bugs, shared_branches, _, shared_specs = ( |
952 | + shared_bugs, shared_branches, shared_gitrepositories, shared_specs = ( |
953 | self.service.getVisibleArtifacts(grantee, bugs=bugs)) |
954 | self.assertContentEqual(bugs[5:], shared_bugs) |
955 | |
956 | |
957 | === modified file 'lib/lp/registry/tests/test_accesspolicy.py' |
958 | --- lib/lp/registry/tests/test_accesspolicy.py 2013-06-20 05:50:00 +0000 |
959 | +++ lib/lp/registry/tests/test_accesspolicy.py 2015-02-27 11:27:39 +0000 |
960 | @@ -1,4 +1,4 @@ |
961 | -# Copyright 2011-2012 Canonical Ltd. This software is licensed under the |
962 | +# Copyright 2011-2015 Canonical Ltd. This software is licensed under the |
963 | # GNU Affero General Public License version 3 (see the file LICENSE). |
964 | |
965 | __metaclass__ = type |
966 | @@ -280,6 +280,13 @@ |
967 | return self.factory.makeBranch() |
968 | |
969 | |
970 | +class TestAccessArtifactGitRepository(BaseAccessArtifactTests, |
971 | + TestCaseWithFactory): |
972 | + |
973 | + def getConcreteArtifact(self): |
974 | + return self.factory.makeGitRepository() |
975 | + |
976 | + |
977 | class TestAccessArtifactBug(BaseAccessArtifactTests, |
978 | TestCaseWithFactory): |
979 | |
980 | |
981 | === modified file 'lib/lp/registry/tests/test_sharingjob.py' |
982 | --- lib/lp/registry/tests/test_sharingjob.py 2014-06-19 06:38:53 +0000 |
983 | +++ lib/lp/registry/tests/test_sharingjob.py 2015-02-27 11:27:39 +0000 |
984 | @@ -1,4 +1,4 @@ |
985 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
986 | +# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
987 | # GNU Affero General Public License version 3 (see the file LICENSE). |
988 | |
989 | """Tests for SharingJobs.""" |
990 | @@ -118,6 +118,16 @@ |
991 | 'for branch_ids=[%d], requestor=%s>' |
992 | % (branch.id, requestor.name), repr(job)) |
993 | |
994 | + def test_repr_gitrepositories(self): |
995 | + requestor = self.factory.makePerson() |
996 | + gitrepository = self.factory.makeGitRepository() |
997 | + job = getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
998 | + requestor, artifacts=[gitrepository]) |
999 | + self.assertEqual( |
1000 | + '<REMOVE_ARTIFACT_SUBSCRIPTIONS job reconciling subscriptions ' |
1001 | + 'for gitrepository_ids=[%d], requestor=%s>' |
1002 | + % (gitrepository.id, requestor.name), repr(job)) |
1003 | + |
1004 | def test_repr_specifications(self): |
1005 | requestor = self.factory.makePerson() |
1006 | specification = self.factory.makeSpecification() |
1007 | @@ -303,6 +313,9 @@ |
1008 | branch = self.factory.makeBranch( |
1009 | owner=owner, product=product, |
1010 | information_type=InformationType.USERDATA) |
1011 | + gitrepository = self.factory.makeGitRepository( |
1012 | + owner=owner, target=product, |
1013 | + information_type=InformationType.USERDATA) |
1014 | specification = self.factory.makeSpecification( |
1015 | owner=owner, product=product, |
1016 | information_type=InformationType.PROPRIETARY) |
1017 | @@ -317,6 +330,8 @@ |
1018 | branch.subscribe(artifact_indirect_grantee, |
1019 | BranchSubscriptionNotificationLevel.NOEMAIL, None, |
1020 | CodeReviewNotificationLevel.NOEMAIL, owner) |
1021 | + # XXX cjwatson 2015-02-05: Fill this in once we have |
1022 | + # GitRepositorySubscription. |
1023 | # Subscribing somebody to a specification does not automatically |
1024 | # create an artifact grant. |
1025 | spec_artifact = self.factory.makeAccessArtifact(specification) |
1026 | @@ -325,10 +340,10 @@ |
1027 | with person_logged_in(product.owner): |
1028 | specification.subscribe(artifact_indirect_grantee, owner) |
1029 | |
1030 | - # pick one of the concrete artifacts (bug, branch or spec) |
1031 | - # and subscribe the teams and persons. |
1032 | + # pick one of the concrete artifacts (bug, branch, Git repository, |
1033 | + # or spec) and subscribe the teams and persons. |
1034 | concrete_artifact, get_pillars, get_subscribers = configure_test( |
1035 | - bug, branch, specification, policy_team_grantee, |
1036 | + bug, branch, gitrepository, specification, policy_team_grantee, |
1037 | policy_indirect_grantee, artifact_team_grantee, owner) |
1038 | |
1039 | # Subscribing policy_team_grantee has created an artifact grant so we |
1040 | @@ -361,6 +376,8 @@ |
1041 | self.assertIn(artifact_team_grantee, subscribers) |
1042 | self.assertIn(artifact_indirect_grantee, bug.getDirectSubscribers()) |
1043 | self.assertIn(artifact_indirect_grantee, branch.subscribers) |
1044 | + # XXX cjwatson 2015-02-05: Fill this in once we have |
1045 | + # GitRepositorySubscription. |
1046 | self.assertIn(artifact_indirect_grantee, |
1047 | removeSecurityProxy(specification).subscribers) |
1048 | |
1049 | @@ -373,9 +390,9 @@ |
1050 | return removeSecurityProxy( |
1051 | concrete_artifact).getDirectSubscribers() |
1052 | |
1053 | - def configure_test(bug, branch, specification, policy_team_grantee, |
1054 | - policy_indirect_grantee, artifact_team_grantee, |
1055 | - owner): |
1056 | + def configure_test(bug, branch, gitrepository, specification, |
1057 | + policy_team_grantee, policy_indirect_grantee, |
1058 | + artifact_team_grantee, owner): |
1059 | concrete_artifact = bug |
1060 | bug.subscribe(policy_team_grantee, owner) |
1061 | bug.subscribe(policy_indirect_grantee, owner) |
1062 | @@ -393,9 +410,9 @@ |
1063 | def get_subscribers(concrete_artifact): |
1064 | return concrete_artifact.subscribers |
1065 | |
1066 | - def configure_test(bug, branch, specification, policy_team_grantee, |
1067 | - policy_indirect_grantee, artifact_team_grantee, |
1068 | - owner): |
1069 | + def configure_test(bug, branch, gitrepository, specification, |
1070 | + policy_team_grantee, policy_indirect_grantee, |
1071 | + artifact_team_grantee, owner): |
1072 | concrete_artifact = branch |
1073 | branch.subscribe( |
1074 | policy_team_grantee, |
1075 | @@ -414,6 +431,27 @@ |
1076 | self._assert_artifact_change_unsubscribes( |
1077 | change_callback, configure_test) |
1078 | |
1079 | + def _assert_gitrepository_change_unsubscribes(self, change_callback): |
1080 | + |
1081 | + def get_pillars(concrete_artifact): |
1082 | + return [concrete_artifact.product] |
1083 | + |
1084 | + def get_subscribers(concrete_artifact): |
1085 | + return concrete_artifact.subscribers |
1086 | + |
1087 | + def configure_test(bug, branch, gitrepository, specification, |
1088 | + policy_team_grantee, policy_indirect_grantee, |
1089 | + artifact_team_grantee, owner): |
1090 | + concrete_artifact = gitrepository |
1091 | + # XXX cjwatson 2015-02-05: Fill this in once we have |
1092 | + # GitRepositorySubscription. |
1093 | + return concrete_artifact, get_pillars, get_subscribers |
1094 | + |
1095 | + # XXX cjwatson 2015-02-05: Uncomment once we have |
1096 | + # GitRepositorySubscription. |
1097 | + #self._assert_artifact_change_unsubscribes( |
1098 | + # change_callback, configure_test) |
1099 | + |
1100 | def _assert_specification_change_unsubscribes(self, change_callback): |
1101 | |
1102 | def get_pillars(concrete_artifact): |
1103 | @@ -422,9 +460,9 @@ |
1104 | def get_subscribers(concrete_artifact): |
1105 | return concrete_artifact.subscribers |
1106 | |
1107 | - def configure_test(bug, branch, specification, policy_team_grantee, |
1108 | - policy_indirect_grantee, artifact_team_grantee, |
1109 | - owner): |
1110 | + def configure_test(bug, branch, gitrepository, specification, |
1111 | + policy_team_grantee, policy_indirect_grantee, |
1112 | + artifact_team_grantee, owner): |
1113 | naked_spec = removeSecurityProxy(specification) |
1114 | naked_spec.subscribe(policy_team_grantee, owner) |
1115 | naked_spec.subscribe(policy_indirect_grantee, owner) |
1116 | @@ -444,6 +482,13 @@ |
1117 | |
1118 | self._assert_branch_change_unsubscribes(change_information_type) |
1119 | |
1120 | + def test_change_information_type_gitrepository(self): |
1121 | + def change_information_type(gitrepository): |
1122 | + removeSecurityProxy(gitrepository).information_type = ( |
1123 | + InformationType.PRIVATESECURITY) |
1124 | + |
1125 | + self._assert_gitrepository_change_unsubscribes(change_information_type) |
1126 | + |
1127 | def test_change_information_type_specification(self): |
1128 | def change_information_type(specification): |
1129 | removeSecurityProxy(specification).information_type = ( |
1130 | |
1131 | === modified file 'lib/lp/security.py' |
1132 | --- lib/lp/security.py 2015-02-23 03:22:37 +0000 |
1133 | +++ lib/lp/security.py 2015-02-27 11:27:39 +0000 |
1134 | @@ -1,4 +1,4 @@ |
1135 | -# Copyright 2009-2014 Canonical Ltd. This software is licensed under the |
1136 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
1137 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1138 | |
1139 | """Security policies for using content objects.""" |
1140 | @@ -83,6 +83,7 @@ |
1141 | ) |
1142 | from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference |
1143 | from lp.code.interfaces.diff import IPreviewDiff |
1144 | +from lp.code.interfaces.gitcollection import IGitCollection |
1145 | from lp.code.interfaces.gitrepository import ( |
1146 | IGitRepository, |
1147 | user_has_special_git_repository_access, |
1148 | @@ -1020,6 +1021,12 @@ |
1149 | if not mp.is_empty(): |
1150 | return True |
1151 | |
1152 | + # Grant visibility to people who can see Git repositories owned |
1153 | + # by the private team. |
1154 | + team_repositories = IGitCollection(self.obj) |
1155 | + if not team_repositories.visibleByUser(user.person).is_empty(): |
1156 | + return True |
1157 | + |
1158 | # Grant visibility to users in a team that has the private team as |
1159 | # a member, so that they can see the team properly in member |
1160 | # listings. |