Merge lp:~twom/launchpad/branch-permissions-for-gitapi into lp:launchpad

Proposed by Tom Wardill
Status: Merged
Merged at revision: 18795
Proposed branch: lp:~twom/launchpad/branch-permissions-for-gitapi
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/git-permissions-model
Diff against target: 909 lines (+824/-0)
6 files modified
lib/lp/code/interfaces/gitapi.py (+6/-0)
lib/lp/code/interfaces/gitrepository.py (+10/-0)
lib/lp/code/model/gitrepository.py (+15/-0)
lib/lp/code/model/tests/test_gitrepository.py (+125/-0)
lib/lp/code/xmlrpc/git.py (+91/-0)
lib/lp/code/xmlrpc/tests/test_git.py (+577/-0)
To merge this branch: bzr merge lp:~twom/launchpad/branch-permissions-for-gitapi
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+355716@code.launchpad.net

This proposal supersedes a proposal from 2018-09-26.

Commit message

Add GitRuleGrant api to xmlrpc API

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) wrote :

I'm unsure on the use of IGitLookup to add an interface for doing the GitRuleGrant lookups, it seemed logical at the time but on reflection it doesn't quite feel to be in the right place/correct structure

Revision history for this message
Colin Watson (cjwatson) wrote :

A quick first-pass review to get you started ...

review: Needs Fixing
Revision history for this message
Colin Watson (cjwatson) :
review: Needs Fixing
Revision history for this message
Colin Watson (cjwatson) wrote :

Thanks, this is looking better now. Just a few more bits and pieces.

A quick pass over the whole diff to conform to the prevailing style would be good (e.g. public methods of model objects get a """See `IFoo`.""" docstring, no blank line at the start of methods, multi-line displays generally have a newline after the opening punctuation and then a four-space indent for their contents rather than the contents being indented to the opening punctuation, etc.).

review: Approve
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Tom Wardill (twom) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/interfaces/gitapi.py'
2--- lib/lp/code/interfaces/gitapi.py 2015-03-31 04:18:22 +0000
3+++ lib/lp/code/interfaces/gitapi.py 2018-10-15 14:44:56 +0000
4@@ -67,3 +67,9 @@
5 :returns: An `Unauthorized` fault, as password authentication is
6 not yet supported.
7 """
8+
9+ def listRefRules(self, repository, user):
10+ """Return the list of ref rules for `user` in `repository`
11+
12+ :returns: A list of rules for the user in the specified repository
13+ """
14
15=== modified file 'lib/lp/code/interfaces/gitrepository.py'
16--- lib/lp/code/interfaces/gitrepository.py 2018-10-12 16:41:14 +0000
17+++ lib/lp/code/interfaces/gitrepository.py 2018-10-15 14:44:56 +0000
18@@ -746,6 +746,16 @@
19 :param user: The `IPerson` who is moving the rule.
20 """
21
22+ def findRuleGrantsByGrantee(grantee):
23+ """Find the grants for a grantee applied to this repository.
24+
25+ :param grantee: The Person affected
26+ """
27+
28+ def findRuleGrantsForRepositoryOwner():
29+ """Find the grants of type REPOSITORY_OWNER applied to this repository.
30+ """
31+
32 @export_read_operation()
33 @operation_for_version("devel")
34 def canBeDeleted():
35
36=== modified file 'lib/lp/code/model/gitrepository.py'
37--- lib/lp/code/model/gitrepository.py 2018-10-12 16:41:14 +0000
38+++ lib/lp/code/model/gitrepository.py 2018-10-15 14:44:56 +0000
39@@ -72,6 +72,7 @@
40 from lp.app.interfaces.services import IService
41 from lp.code.enums import (
42 BranchMergeProposalStatus,
43+ GitGranteeType,
44 GitObjectType,
45 GitRepositoryType,
46 )
47@@ -1193,6 +1194,20 @@
48 return Store.of(self).find(
49 GitRuleGrant, GitRuleGrant.repository_id == self.id)
50
51+ def findRuleGrantsByGrantee(self, grantee):
52+ """See `IGitRepository`."""
53+ clauses = [
54+ GitRuleGrant.grantee_type == GitGranteeType.PERSON,
55+ TeamParticipation.person == grantee,
56+ GitRuleGrant.grantee == TeamParticipation.teamID
57+ ]
58+ return self.grants.find(*clauses).config(distinct=True)
59+
60+ def findRuleGrantsForRepositoryOwner(self):
61+ """See `IGitRepository`."""
62+ return self.grants.find(
63+ GitRuleGrant.grantee_type == GitGranteeType.REPOSITORY_OWNER)
64+
65 def getActivity(self):
66 """See `IGitRepository`."""
67 clauses = [GitActivity.repository_id == self.id]
68
69=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
70--- lib/lp/code/model/tests/test_gitrepository.py 2018-10-12 16:41:14 +0000
71+++ lib/lp/code/model/tests/test_gitrepository.py 2018-10-15 14:44:56 +0000
72@@ -47,6 +47,7 @@
73 BranchSubscriptionDiffSize,
74 BranchSubscriptionNotificationLevel,
75 CodeReviewNotificationLevel,
76+ GitGranteeType,
77 GitObjectType,
78 GitRepositoryType,
79 TargetRevisionControlSystems,
80@@ -224,6 +225,130 @@
81 bmp = self.factory.makeBranchMergeProposalForGit(target_ref=ref)
82 self.assertEqual([bmp], list(repository.getMergeProposals()))
83
84+ def test_findRuleGrantsByGrantee_person(self):
85+ requester = self.factory.makePerson()
86+ repository = removeSecurityProxy(
87+ self.factory.makeGitRepository(owner=requester))
88+
89+ rule = self.factory.makeGitRule(repository)
90+ grant = self.factory.makeGitRuleGrant(
91+ rule=rule, grantee=requester, can_push=True, can_create=True)
92+
93+ results = repository.findRuleGrantsByGrantee(requester)
94+ self.assertEqual([grant], list(results))
95+
96+ def test_findRuleGrantsByGrantee_team(self):
97+ requester = self.factory.makeTeam()
98+ repository = removeSecurityProxy(
99+ self.factory.makeGitRepository(owner=requester))
100+
101+ rule = self.factory.makeGitRule(repository)
102+ grant = self.factory.makeGitRuleGrant(
103+ rule=rule, grantee=requester, can_push=True, can_create=True)
104+
105+ results = repository.findRuleGrantsByGrantee(requester)
106+ self.assertEqual([grant], list(results))
107+
108+ def test_findRuleGrantsByGrantee_member_of_team(self):
109+ member = self.factory.makePerson()
110+ requester = self.factory.makeTeam(members=[member])
111+ repository = removeSecurityProxy(
112+ self.factory.makeGitRepository(owner=requester))
113+
114+ rule = self.factory.makeGitRule(repository)
115+ grant = self.factory.makeGitRuleGrant(
116+ rule=rule, grantee=requester, can_push=True, can_create=True)
117+
118+ results = repository.findRuleGrantsByGrantee(requester)
119+ self.assertEqual([grant], list(results))
120+
121+ def test_findRuleGrantsByGrantee_team_in_team(self):
122+ member = self.factory.makePerson()
123+ team = self.factory.makeTeam(owner=member, members=[member])
124+ top_level = removeSecurityProxy(self.factory.makeTeam())
125+ top_level.addMember(team, top_level.teamowner, force_team_add=True)
126+
127+ repository = removeSecurityProxy(
128+ self.factory.makeGitRepository(owner=top_level))
129+
130+ rule = self.factory.makeGitRule(repository)
131+ grant = self.factory.makeGitRuleGrant(
132+ rule=rule, grantee=top_level, can_push=True, can_create=True)
133+
134+ results = repository.findRuleGrantsByGrantee(member)
135+ self.assertEqual([grant], list(results))
136+
137+ def test_findRuleGrantsByGrantee_team_in_team_not_owner(self):
138+ member = self.factory.makePerson()
139+ team = self.factory.makeTeam(owner=member, members=[member])
140+ top_level = removeSecurityProxy(self.factory.makeTeam())
141+ top_level.addMember(team, top_level.teamowner, force_team_add=True)
142+
143+ repository = removeSecurityProxy(
144+ self.factory.makeGitRepository())
145+
146+ rule = self.factory.makeGitRule(repository)
147+ grant = self.factory.makeGitRuleGrant(
148+ rule=rule, grantee=top_level, can_push=True, can_create=True)
149+
150+ results = repository.findRuleGrantsByGrantee(member)
151+ self.assertEqual([grant], list(results))
152+
153+ def test_findRuleGrantsByGrantee_not_owner(self):
154+ requester = self.factory.makePerson()
155+ repository = removeSecurityProxy(
156+ self.factory.makeGitRepository())
157+
158+ rule = self.factory.makeGitRule(repository)
159+ grant = self.factory.makeGitRuleGrant(
160+ rule=rule, grantee=requester, can_push=True, can_create=True)
161+
162+ results = repository.findRuleGrantsByGrantee(requester)
163+ self.assertEqual([grant], list(results))
164+
165+ def test_findRuleGrantsByGrantee_grantee_type(self):
166+ requester = self.factory.makePerson()
167+ repository = removeSecurityProxy(
168+ self.factory.makeGitRepository(owner=requester))
169+
170+ rule = self.factory.makeGitRule(repository)
171+ grant = self.factory.makeGitRuleGrant(
172+ rule=rule,
173+ grantee=GitGranteeType.REPOSITORY_OWNER,
174+ can_push=True,
175+ can_create=True)
176+
177+ results = repository.findRuleGrantsForRepositoryOwner()
178+ self.assertEqual([grant], list(results))
179+
180+ def test_findRuleGrantsByGrantee_owner_and_other(self):
181+ requester = self.factory.makePerson()
182+ other = self.factory.makePerson()
183+ repository = removeSecurityProxy(
184+ self.factory.makeGitRepository(owner=requester))
185+
186+ rule = self.factory.makeGitRule(repository)
187+ self.factory.makeGitRuleGrant(
188+ rule=rule, grantee=other, can_push=True, can_create=True)
189+
190+ results = repository.findRuleGrantsByGrantee(requester)
191+ self.assertEqual([], list(results))
192+
193+ def test_findRuleGrantsByGrantee_owner_and_other_with_owner_grant(self):
194+ requester = self.factory.makePerson()
195+ other = self.factory.makePerson()
196+ repository = removeSecurityProxy(
197+ self.factory.makeGitRepository(owner=requester))
198+
199+ rule = self.factory.makeGitRule(repository)
200+ self.factory.makeGitRuleGrant(
201+ rule=rule, grantee=other, can_push=True, can_create=True)
202+ owner_grant = self.factory.makeGitRuleGrant(
203+ rule=rule, grantee=requester, can_push=True)
204+
205+ results = repository.findRuleGrantsByGrantee(requester)
206+ self.assertEqual([owner_grant], list(results))
207+
208
209 class TestGitIdentityMixin(TestCaseWithFactory):
210 """Test the defaults and identities provided by GitIdentityMixin."""
211
212=== modified file 'lib/lp/code/xmlrpc/git.py'
213--- lib/lp/code/xmlrpc/git.py 2018-08-28 13:58:37 +0000
214+++ lib/lp/code/xmlrpc/git.py 2018-10-15 14:44:56 +0000
215@@ -325,3 +325,94 @@
216 else:
217 # Only macaroons are supported for password authentication.
218 return faults.Unauthorized()
219+
220+ def _sortPermissions(self, set_of_permissions):
221+ """Sort an iterable of permission strings for consistency"""
222+ permissions = []
223+ if 'create' in set_of_permissions:
224+ permissions.append('create')
225+ if 'push' in set_of_permissions:
226+ permissions.append('push')
227+ if 'force_push' in set_of_permissions:
228+ permissions.append('force_push')
229+ return permissions
230+
231+ def _buildPermissions(self, grant):
232+ """Build a set of the available permissions from a GitRuleGrant"""
233+ permissions = set()
234+ if grant.can_create:
235+ permissions.add('create')
236+ if grant.can_push:
237+ permissions.add('push')
238+ if grant.can_force_push:
239+ # can_force_push implies can_push.
240+ permissions.add('push')
241+ permissions.add('force_push')
242+ return permissions
243+
244+ def _listRefRules(self, requester, translated_path):
245+ repository = removeSecurityProxy(
246+ getUtility(IGitLookup).getByHostingPath(translated_path))
247+ is_owner = requester.inTeam(repository.owner)
248+ grants = repository.findRuleGrantsByGrantee(requester)
249+ # If the user is the owner, get the grants for REPOSITORY_OWNER
250+ # and add them to our available grants to match against
251+ if is_owner:
252+ owner_grants = repository.findRuleGrantsForRepositoryOwner()
253+ grants = grants.union(owner_grants)
254+ result = []
255+
256+ for rule in repository.rules:
257+ # Do we have any grants for this rule, for this user?
258+ matching_grants = [x for x in grants if x.rule == rule]
259+ # If we don't have any grants, but the user is the owner,
260+ # they get a default grant to the ref specified by the rule.
261+ if is_owner and not matching_grants:
262+ result.append(
263+ {'ref_pattern': rule.ref_pattern,
264+ 'permissions': ['create', 'push'],
265+ })
266+ continue
267+ # If we otherwise don't have any matching grants,
268+ # we can ignore this rule
269+ if not matching_grants:
270+ continue
271+
272+ # Permissions are a union of all the applicable grants
273+ union_permissions = set()
274+ for grant in matching_grants:
275+ permissions = self._buildPermissions(grant)
276+ union_permissions.update(permissions)
277+
278+ # If the user is the repository owner, they essentially have
279+ # the equivalent of a default team grant, but only
280+ # if there is no explicit grant to them otherwise specified
281+ if is_owner and not any(g for g in owner_grants if g.rule == rule):
282+ union_permissions.update(['create', 'push'])
283+
284+ # Sort the permissions from the set for consistency
285+ sorted_permissions = self._sortPermissions(union_permissions)
286+ result.append(
287+ {'ref_pattern': rule.ref_pattern,
288+ 'permissions': sorted_permissions,
289+ })
290+
291+ # The last rule for a repository owner is a default permission
292+ # for everything. This is overridden by any matching rules previously
293+ # in the list
294+ if is_owner:
295+ result.append(
296+ {'ref_pattern': '*',
297+ 'permissions': ['create', 'push', 'force_push'],
298+ })
299+
300+ return result
301+
302+ def listRefRules(self, translated_path, auth_params):
303+ """See `IGitAPI`"""
304+ requester_id = auth_params.get("uid")
305+ return run_with_login(
306+ requester_id,
307+ self._listRefRules,
308+ translated_path,
309+ )
310
311=== modified file 'lib/lp/code/xmlrpc/tests/test_git.py'
312--- lib/lp/code/xmlrpc/tests/test_git.py 2018-08-28 14:07:38 +0000
313+++ lib/lp/code/xmlrpc/tests/test_git.py 2018-10-15 14:44:56 +0000
314@@ -6,11 +6,17 @@
315 __metaclass__ = type
316
317 from pymacaroons import Macaroon
318+from testtools.matchers import (
319+ Equals,
320+ MatchesListwise,
321+ MatchesDict,
322+ )
323 from zope.component import getUtility
324 from zope.security.proxy import removeSecurityProxy
325
326 from lp.app.enums import InformationType
327 from lp.code.enums import (
328+ GitGranteeType,
329 GitRepositoryType,
330 TargetRevisionControlSystems,
331 )
332@@ -260,6 +266,577 @@
333 self.assertEqual(
334 initial_count, getUtility(IAllGitRepositories).count())
335
336+ def test_listRefRules_simple(self):
337+ # Test that correct ref rules are retrieved for a Person
338+ requester = self.factory.makePerson()
339+ repository = removeSecurityProxy(
340+ self.factory.makeGitRepository())
341+
342+ rule = self.factory.makeGitRule(repository)
343+ self.factory.makeGitRuleGrant(
344+ rule=rule, grantee=requester, can_push=True, can_create=True)
345+
346+ results = self.git_api.listRefRules(
347+ repository.getInternalPath(),
348+ {'uid': requester.id})
349+ self.assertThat(results, MatchesListwise([
350+ MatchesDict({
351+ 'ref_pattern': Equals('refs/heads/*'),
352+ 'permissions': Equals(['create', 'push']),
353+ }),
354+ ]))
355+
356+ def test_listRefRules_with_other_grants(self):
357+ # Test that findRuleGrantsByGrantee only returns relevant rules
358+ requester = self.factory.makePerson()
359+ other_user = self.factory.makePerson()
360+ repository = removeSecurityProxy(
361+ self.factory.makeGitRepository())
362+
363+ rule = self.factory.makeGitRule(repository)
364+ self.factory.makeGitRuleGrant(
365+ rule=rule, grantee=requester, can_push=True)
366+ self.factory.makeGitRuleGrant(
367+ rule=rule, grantee=other_user, can_create=True)
368+
369+ results = self.git_api.listRefRules(
370+ repository.getInternalPath(),
371+ {'uid': requester.id})
372+ self.assertThat(results, MatchesListwise([
373+ MatchesDict({
374+ 'ref_pattern': Equals('refs/heads/*'),
375+ 'permissions': Equals(['push']),
376+ })
377+ ]))
378+
379+ def test_listRefRules_owner_has_default(self):
380+ owner = self.factory.makePerson()
381+ repository = removeSecurityProxy(
382+ self.factory.makeGitRepository(owner=owner))
383+
384+ results = self.git_api.listRefRules(
385+ repository.getInternalPath(),
386+ {'uid': owner.id})
387+
388+ self.assertThat(results, MatchesListwise([
389+ MatchesDict({
390+ 'ref_pattern': Equals('*'),
391+ 'permissions': Equals(['create', 'push', 'force_push']),
392+ }),
393+ ]))
394+
395+ def test_listRefRules_owner_modifies_rules(self):
396+ owner = self.factory.makePerson()
397+ person = self.factory.makePerson()
398+ repository = removeSecurityProxy(
399+ self.factory.makeGitRepository(owner=owner))
400+
401+ rule = self.factory.makeGitRule(
402+ repository, ref_pattern=u'refs/heads/stable/*')
403+ self.factory.makeGitRuleGrant(
404+ rule=rule, grantee=person, can_push=True)
405+
406+ results = self.git_api.listRefRules(
407+ repository.getInternalPath(),
408+ {'uid': owner.id})
409+
410+ self.assertThat(results, MatchesListwise([
411+ MatchesDict({
412+ 'ref_pattern': Equals('refs/heads/stable/*'),
413+ 'permissions': Equals(['create', 'push']),
414+ }),
415+ MatchesDict({
416+ 'ref_pattern': Equals('*'),
417+ 'permissions': Equals(['create', 'push', 'force_push']),
418+ }),
419+ ]))
420+
421+ def test_listRefRules_owner_no_default_with_explicit(self):
422+ owner = self.factory.makePerson()
423+ repository = removeSecurityProxy(
424+ self.factory.makeGitRepository(owner=owner))
425+
426+ rule = self.factory.makeGitRule(
427+ repository, ref_pattern=u'refs/heads/stable/*')
428+ self.factory.makeGitRuleGrant(
429+ rule=rule, grantee=GitGranteeType.REPOSITORY_OWNER, can_push=True)
430+
431+ results = self.git_api.listRefRules(
432+ repository.getInternalPath(),
433+ {'uid': owner.id})
434+
435+ self.assertThat(results, MatchesListwise([
436+ MatchesDict({
437+ 'ref_pattern': Equals('refs/heads/stable/*'),
438+ 'permissions': Equals(['push']),
439+ }),
440+ MatchesDict({
441+ 'ref_pattern': Equals('*'),
442+ 'permissions': Equals(['create', 'push', 'force_push']),
443+ }),
444+ ]))
445+
446+ def test_listRefRules_owner_modifies_rules_multiple_grants(self):
447+ owner = self.factory.makePerson()
448+ person = self.factory.makePerson()
449+ repository = removeSecurityProxy(
450+ self.factory.makeGitRepository(owner=owner))
451+
452+ rule = self.factory.makeGitRule(
453+ repository, ref_pattern=u'refs/heads/stable/*')
454+ self.factory.makeGitRuleGrant(
455+ rule=rule, grantee=person, can_push=True, can_create=True)
456+
457+ self.factory.makeGitRuleGrant(
458+ rule=rule, grantee=GitGranteeType.REPOSITORY_OWNER, can_push=True)
459+
460+ results = self.git_api.listRefRules(
461+ repository.getInternalPath(),
462+ {'uid': owner.id})
463+
464+ self.assertThat(results, MatchesListwise([
465+ MatchesDict({
466+ 'ref_pattern': Equals('refs/heads/stable/*'),
467+ 'permissions': Equals(['push']),
468+ }),
469+ MatchesDict({
470+ 'ref_pattern': Equals('*'),
471+ 'permissions': Equals(['create', 'push', 'force_push']),
472+ }),
473+ ]))
474+
475+ def test_listRefRules_no_grants(self):
476+ # User that has no grants and is not the owner
477+ requester = self.factory.makePerson()
478+ owner = self.factory.makePerson()
479+ repository = removeSecurityProxy(
480+ self.factory.makeGitRepository(owner=owner))
481+
482+ rule = self.factory.makeGitRule(repository)
483+ self.factory.makeGitRuleGrant(
484+ rule=rule, grantee=owner, can_push=True, can_create=True)
485+
486+ results = self.git_api.listRefRules(
487+ repository.getInternalPath(),
488+ {'uid': requester.id})
489+ self.assertEqual(0, len(results))
490+
491+ def test_listRefRules_owner_has_default_with_other_grant(self):
492+ owner = self.factory.makePerson()
493+ repository = removeSecurityProxy(
494+ self.factory.makeGitRepository(owner=owner))
495+
496+ rule = self.factory.makeGitRule(
497+ repository=repository, ref_pattern=u'refs/heads/master')
498+ self.factory.makeGitRuleGrant(
499+ rule=rule, grantee=owner, can_push=True, can_create=True)
500+
501+ results = self.git_api.listRefRules(
502+ repository.getInternalPath(),
503+ {'uid': owner.id})
504+ self.assertEqual(len(results), 2)
505+ # Default grant should be last in pattern
506+ self.assertThat(results, MatchesListwise([
507+ MatchesDict({
508+ 'ref_pattern': Equals('refs/heads/master'),
509+ 'permissions': Equals(['create', 'push']),
510+ }),
511+ MatchesDict({
512+ 'ref_pattern': Equals('*'),
513+ 'permissions': Equals(['create', 'push', 'force_push']),
514+ }),
515+ ]))
516+
517+ def test_listRefRules_owner_is_team(self):
518+ member = self.factory.makePerson()
519+ owner = self.factory.makeTeam(members=[member])
520+ repository = removeSecurityProxy(
521+ self.factory.makeGitRepository(
522+ owner=owner, information_type=InformationType.USERDATA))
523+
524+ results = self.git_api.listRefRules(
525+ repository.getInternalPath(),
526+ {'uid': member.id})
527+
528+ # Should have default grant as member of owning team
529+ self.assertThat(results, MatchesListwise([
530+ MatchesDict({
531+ 'ref_pattern': Equals('*'),
532+ 'permissions': Equals(['create', 'push', 'force_push']),
533+ }),
534+ ]))
535+
536+ def test_listRefRules_owner_is_team_with_grants(self):
537+ member = self.factory.makePerson()
538+ owner = self.factory.makeTeam(members=[member])
539+ repository = removeSecurityProxy(
540+ self.factory.makeGitRepository(
541+ owner=owner, information_type=InformationType.USERDATA))
542+
543+ rule = self.factory.makeGitRule(
544+ repository=repository, ref_pattern=u'refs/heads/master')
545+ self.factory.makeGitRuleGrant(
546+ rule=rule, grantee=owner, can_push=True, can_create=True)
547+
548+ results = self.git_api.listRefRules(
549+ repository.getInternalPath(),
550+ {'uid': member.id})
551+
552+ # Should have default grant as member of owning team
553+ self.assertThat(results, MatchesListwise([
554+ MatchesDict({
555+ 'ref_pattern': Equals('refs/heads/master'),
556+ 'permissions': Equals(['create', 'push']),
557+ }),
558+ MatchesDict({
559+ 'ref_pattern': Equals('*'),
560+ 'permissions': Equals(['create', 'push', 'force_push']),
561+ }),
562+ ]))
563+
564+ def test_listRefRules_owner_is_team_with_grants_to_person(self):
565+ member = self.factory.makePerson()
566+ other_member = self.factory.makePerson()
567+ owner = self.factory.makeTeam(members=[member, other_member])
568+ repository = removeSecurityProxy(
569+ self.factory.makeGitRepository(
570+ owner=owner, information_type=InformationType.USERDATA))
571+
572+ rule = self.factory.makeGitRule(
573+ repository=repository, ref_pattern=u'refs/heads/master')
574+ self.factory.makeGitRuleGrant(
575+ rule=rule, grantee=GitGranteeType.REPOSITORY_OWNER,
576+ can_push=True, can_create=True)
577+
578+ rule = self.factory.makeGitRule(
579+ repository=repository, ref_pattern=u'refs/heads/tags')
580+ self.factory.makeGitRuleGrant(
581+ rule=rule, grantee=member, can_create=True)
582+
583+ # This should not appear
584+ self.factory.makeGitRuleGrant(
585+ rule=rule, grantee=other_member, can_push=True)
586+
587+ results = self.git_api.listRefRules(
588+ repository.getInternalPath(),
589+ {'uid': member.id})
590+
591+ # Should have default grant as member of owning team
592+ self.assertThat(results, MatchesListwise([
593+ MatchesDict({
594+ 'ref_pattern': Equals('refs/heads/master'),
595+ 'permissions': Equals(['create', 'push']),
596+ }),
597+ MatchesDict({
598+ 'ref_pattern': Equals('refs/heads/tags'),
599+ 'permissions': Equals(['create', 'push']),
600+ }),
601+ MatchesDict({
602+ 'ref_pattern': Equals('*'),
603+ 'permissions': Equals(['create', 'push', 'force_push']),
604+ }),
605+ ]))
606+
607+ def test_listRefRules_multiple_grants_to_same_ref_with_owner(self):
608+ member = self.factory.makePerson()
609+ owner = self.factory.makeTeam(members=[member])
610+ repository = removeSecurityProxy(
611+ self.factory.makeGitRepository(owner=owner))
612+
613+ rule = self.factory.makeGitRule(repository=repository)
614+ self.factory.makeGitRuleGrant(
615+ rule=rule, grantee=member, can_create=True)
616+ self.factory.makeGitRuleGrant(
617+ rule=rule, grantee=owner, can_push=True)
618+
619+ results = self.git_api.listRefRules(
620+ repository.getInternalPath(),
621+ {'uid': member.id})
622+
623+ self.assertThat(results, MatchesListwise([
624+ MatchesDict({
625+ 'ref_pattern': Equals('refs/heads/*'),
626+ 'permissions': Equals(['create', 'push']),
627+ }),
628+ MatchesDict({
629+ 'ref_pattern': Equals('*'),
630+ 'permissions': Equals(['create', 'push', 'force_push']),
631+ }),
632+ ]))
633+
634+ def test_listRefRules_multiple_grants_collapsing(self):
635+ member = self.factory.makePerson()
636+ second_member = self.factory.makePerson()
637+ third_member = self.factory.makePerson()
638+ owner = self.factory.makePerson()
639+ repository = removeSecurityProxy(
640+ self.factory.makeGitRepository(owner=owner))
641+
642+ rule = self.factory.makeGitRule(repository=repository)
643+ self.factory.makeGitRuleGrant(
644+ rule=rule, grantee=member, can_create=True)
645+ self.factory.makeGitRuleGrant(
646+ rule=rule, grantee=second_member, can_push=True)
647+ self.factory.makeGitRuleGrant(
648+ rule=rule, grantee=third_member, can_force_push=True)
649+
650+ results = self.git_api.listRefRules(
651+ repository.getInternalPath(),
652+ {'uid': owner.id})
653+
654+ self.assertThat(results, MatchesListwise([
655+ MatchesDict({
656+ 'ref_pattern': Equals('refs/heads/*'),
657+ 'permissions': Equals(['create', 'push']),
658+ }),
659+ MatchesDict({
660+ 'ref_pattern': Equals('*'),
661+ 'permissions': Equals(['create', 'push', 'force_push']),
662+ }),
663+ ]))
664+
665+ def test_listRefRules_grantee_owner_type(self):
666+ owner = self.factory.makePerson()
667+ repository = removeSecurityProxy(
668+ self.factory.makeGitRepository(owner=owner))
669+ rule = self.factory.makeGitRule(repository=repository)
670+ self.factory.makeGitRuleGrant(
671+ rule=rule, grantee=GitGranteeType.REPOSITORY_OWNER,
672+ can_create=True)
673+
674+ results = self.git_api.listRefRules(
675+ repository.getInternalPath(),
676+ {'uid': owner.id})
677+
678+ self.assertThat(results, MatchesListwise([
679+ MatchesDict({
680+ 'ref_pattern': Equals('refs/heads/*'),
681+ 'permissions': Equals(['create']),
682+ }),
683+ MatchesDict({
684+ 'ref_pattern': Equals('*'),
685+ 'permissions': Equals(['create', 'push', 'force_push']),
686+ }),
687+ ]))
688+
689+ def test_listRefRules_grantee_owner_type_and_other_grants(self):
690+ owner = self.factory.makePerson()
691+ other_person = self.factory.makePerson()
692+ repository = removeSecurityProxy(
693+ self.factory.makeGitRepository(owner=owner))
694+ rule = self.factory.makeGitRule(repository=repository)
695+ self.factory.makeGitRuleGrant(
696+ rule=rule, grantee=GitGranteeType.REPOSITORY_OWNER,
697+ can_create=True)
698+
699+ rule = self.factory.makeGitRule(
700+ repository=repository, ref_pattern=u'refs/heads/other')
701+ self.factory.makeGitRuleGrant(
702+ rule=rule, grantee=other_person, can_push=True)
703+
704+ results = self.git_api.listRefRules(
705+ repository.getInternalPath(),
706+ {'uid': owner.id})
707+
708+ self.assertThat(results, MatchesListwise([
709+ MatchesDict({
710+ 'ref_pattern': Equals('refs/heads/other'),
711+ 'permissions': Equals(['create', 'push']),
712+ }),
713+ MatchesDict({
714+ 'ref_pattern': Equals('refs/heads/*'),
715+ 'permissions': Equals(['create']),
716+ }),
717+ MatchesDict({
718+ 'ref_pattern': Equals('*'),
719+ 'permissions': Equals(['create', 'push', 'force_push']),
720+ }),
721+ ]))
722+
723+ def test_listRefRules_grantee_example_one(self):
724+ user_a = self.factory.makePerson()
725+ user_b = self.factory.makePerson()
726+ user_c = self.factory.makePerson()
727+ stable_team = self.factory.makeTeam(members=[user_a, user_b])
728+ next_team = self.factory.makeTeam(members=[user_b, user_c])
729+
730+ repository = removeSecurityProxy(
731+ self.factory.makeGitRepository(owner=user_a))
732+
733+ rule = self.factory.makeGitRule(
734+ repository, ref_pattern=u'refs/heads/stable/next')
735+ self.factory.makeGitRuleGrant(
736+ rule=rule, grantee=GitGranteeType.REPOSITORY_OWNER,
737+ can_force_push=True)
738+
739+ rule = self.factory.makeGitRule(
740+ repository, ref_pattern=u'refs/heads/archived/*')
741+ self.factory.makeGitRuleGrant(
742+ rule=rule, grantee=GitGranteeType.REPOSITORY_OWNER)
743+ self.factory.makeGitRuleGrant(
744+ rule=rule, grantee=user_b, can_create=True)
745+
746+ rule = self.factory.makeGitRule(
747+ repository, ref_pattern=u'refs/heads/stable/*')
748+ self.factory.makeGitRuleGrant(
749+ rule=rule, grantee=stable_team, can_push=True)
750+
751+ rule = self.factory.makeGitRule(
752+ repository, ref_pattern=u'refs/heads/*/next')
753+ self.factory.makeGitRuleGrant(
754+ rule=rule, grantee=next_team, can_force_push=True)
755+
756+ rule = self.factory.makeGitRule(
757+ repository, ref_pattern=u'refs/tags/*')
758+ self.factory.makeGitRuleGrant(
759+ rule=rule, grantee=GitGranteeType.REPOSITORY_OWNER,
760+ can_create=True)
761+ self.factory.makeGitRuleGrant(
762+ rule=rule, grantee=stable_team, can_create=True)
763+
764+ results = self.git_api.listRefRules(
765+ repository.getInternalPath(),
766+ {'uid': user_a.id})
767+
768+ self.assertThat(results, MatchesListwise([
769+ MatchesDict(
770+ {'ref_pattern': Equals('refs/heads/stable/next'),
771+ 'permissions': Equals(['push', 'force_push']),
772+ }),
773+ MatchesDict(
774+ {'ref_pattern': Equals('refs/heads/archived/*'),
775+ 'permissions': Equals([]),
776+ }),
777+ MatchesDict(
778+ {'ref_pattern': Equals('refs/heads/stable/*'),
779+ 'permissions': Equals(['create', 'push']),
780+ }),
781+ MatchesDict(
782+ {'ref_pattern': Equals('refs/heads/*/next'),
783+ 'permissions': Equals(['create', 'push']),
784+ }),
785+ MatchesDict(
786+ {'ref_pattern': Equals('refs/tags/*'),
787+ 'permissions': Equals(['create']),
788+ }),
789+ MatchesDict(
790+ {'ref_pattern': Equals('*'),
791+ 'permissions': Equals(['create', 'push', 'force_push']),
792+ }),
793+ ]))
794+
795+ def test_listRefRules_grantee_example_two(self):
796+ user_a = self.factory.makePerson()
797+ user_b = self.factory.makePerson()
798+ user_c = self.factory.makePerson()
799+ stable_team = self.factory.makeTeam(members=[user_a, user_b])
800+ next_team = self.factory.makeTeam(members=[user_b, user_c])
801+
802+ repository = removeSecurityProxy(
803+ self.factory.makeGitRepository(owner=user_a))
804+
805+ rule = self.factory.makeGitRule(
806+ repository, ref_pattern=u'refs/heads/stable/next')
807+ self.factory.makeGitRuleGrant(
808+ rule=rule, grantee=user_a, can_force_push=True)
809+
810+ rule = self.factory.makeGitRule(
811+ repository, ref_pattern=u'refs/heads/archived/*')
812+ self.factory.makeGitRuleGrant(
813+ rule=rule, grantee=user_a)
814+ self.factory.makeGitRuleGrant(
815+ rule=rule, grantee=user_b, can_create=True)
816+
817+ rule = self.factory.makeGitRule(
818+ repository, ref_pattern=u'refs/heads/stable/*')
819+ self.factory.makeGitRuleGrant(
820+ rule=rule, grantee=stable_team, can_push=True)
821+
822+ rule = self.factory.makeGitRule(
823+ repository, ref_pattern=u'refs/heads/*/next')
824+ self.factory.makeGitRuleGrant(
825+ rule=rule, grantee=next_team, can_force_push=True)
826+
827+ rule = self.factory.makeGitRule(
828+ repository, ref_pattern=u'refs/tags/*')
829+ self.factory.makeGitRuleGrant(
830+ rule=rule, grantee=user_a, can_create=True)
831+ self.factory.makeGitRuleGrant(
832+ rule=rule, grantee=stable_team, can_create=True)
833+
834+ results = self.git_api.listRefRules(
835+ repository.getInternalPath(),
836+ {'uid': user_b.id})
837+
838+ self.assertThat(results, MatchesListwise([
839+ MatchesDict(
840+ {'ref_pattern': Equals('refs/heads/archived/*'),
841+ 'permissions': Equals(['create']),
842+ }),
843+ MatchesDict(
844+ {'ref_pattern': Equals('refs/heads/stable/*'),
845+ 'permissions': Equals(['push']),
846+ }),
847+ MatchesDict(
848+ {'ref_pattern': Equals('refs/heads/*/next'),
849+ 'permissions': Equals(['push', 'force_push']),
850+ }),
851+ MatchesDict(
852+ {'ref_pattern': Equals('refs/tags/*'),
853+ 'permissions': Equals(['create']),
854+ }),
855+ ]))
856+
857+ def test_listRefRules_grantee_example_three(self):
858+ user_a = self.factory.makePerson()
859+ user_b = self.factory.makePerson()
860+ user_c = self.factory.makePerson()
861+ stable_team = self.factory.makeTeam(members=[user_a, user_b])
862+ next_team = self.factory.makeTeam(members=[user_b, user_c])
863+
864+ repository = removeSecurityProxy(
865+ self.factory.makeGitRepository(owner=user_a))
866+
867+ rule = self.factory.makeGitRule(
868+ repository, ref_pattern=u'refs/heads/stable/next')
869+ self.factory.makeGitRuleGrant(
870+ rule=rule, grantee=user_a, can_force_push=True)
871+
872+ rule = self.factory.makeGitRule(
873+ repository, ref_pattern=u'refs/heads/archived/*')
874+ self.factory.makeGitRuleGrant(
875+ rule=rule, grantee=user_a)
876+ self.factory.makeGitRuleGrant(
877+ rule=rule, grantee=user_b, can_create=True)
878+
879+ rule = self.factory.makeGitRule(
880+ repository, ref_pattern=u'refs/heads/stable/*')
881+ self.factory.makeGitRuleGrant(
882+ rule=rule, grantee=stable_team, can_push=True)
883+
884+ rule = self.factory.makeGitRule(
885+ repository, ref_pattern=u'refs/heads/*/next')
886+ self.factory.makeGitRuleGrant(
887+ rule=rule, grantee=next_team, can_force_push=True)
888+
889+ rule = self.factory.makeGitRule(
890+ repository, ref_pattern=u'refs/tags/*')
891+ self.factory.makeGitRuleGrant(
892+ rule=rule, grantee=user_a, can_create=True)
893+ self.factory.makeGitRuleGrant(
894+ rule=rule, grantee=stable_team, can_create=True)
895+
896+ results = self.git_api.listRefRules(
897+ repository.getInternalPath(),
898+ {'uid': user_c.id})
899+
900+ self.assertThat(results, MatchesListwise([
901+ MatchesDict(
902+ {'ref_pattern': Equals('refs/heads/*/next'),
903+ 'permissions': Equals(['push', 'force_push']),
904+ }),
905+ ]))
906+
907
908 class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
909 """Tests for the implementation of `IGitAPI`."""