Merge lp:~cjwatson/launchpad/git-finish-sharing into lp:launchpad

Proposed by Colin Watson on 2015-02-23
Status: Merged
Approved by: Colin Watson on 2015-02-27
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
Reviewer Review Type Date Requested Status
William Grant code 2015-02-23 Approve on 2015-02-26
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.
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.