launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #17941
[Merge] lp:~cjwatson/launchpad/git-finish-sharing into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/git-finish-sharing into lp:launchpad with lp:~cjwatson/launchpad/git-collection as a prerequisite.
Commit message:
Use Git repository collections to implement visibility and sharing.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1032731 in Launchpad itself: "Support for Launchpad-hosted Git repositories"
https://bugs.launchpad.net/launchpad/+bug/1032731
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/git-finish-sharing/+merge/250663
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.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/git-finish-sharing into lp:launchpad.
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2015-02-23 18:07:51 +0000
+++ lib/lp/code/model/gitrepository.py 2015-02-23 18:07:51 +0000
@@ -47,6 +47,7 @@
InvalidGitRepositoryException,
InvalidNamespace,
)
+from lp.code.interfaces.gitcollection import IAllGitRepositories
from lp.code.interfaces.gitlookup import IGitLookup
from lp.code.interfaces.gitnamespace import (
get_git_namespace,
@@ -303,9 +304,8 @@
elif user.id in self._known_viewers:
return True
else:
- # XXX cjwatson 2015-02-06: Fill this in once IGitCollection is
- # in place.
- return False
+ return not getUtility(IAllGitRepositories).withIds(
+ self.id).visibleByUser(user).is_empty()
def getAllowedInformationTypes(self, user):
"""See `IGitRepository`."""
=== modified file 'lib/lp/code/model/hasgitrepositories.py'
--- lib/lp/code/model/hasgitrepositories.py 2015-02-10 01:03:48 +0000
+++ lib/lp/code/model/hasgitrepositories.py 2015-02-23 18:07:51 +0000
@@ -8,6 +8,7 @@
from zope.component import getUtility
+from lp.code.interfaces.gitcollection import IGitCollection
from lp.code.interfaces.gitrepository import IGitRepositorySet
@@ -23,6 +24,5 @@
def getGitRepositories(self, visible_by_user=None, eager_load=False):
"""See `IHasGitRepositories`."""
- # XXX cjwatson 2015-02-06: Fill this in once IGitCollection is in
- # place.
- raise NotImplementedError
+ collection = IGitCollection(self).visibleByUser(visible_by_user)
+ return collection.getRepositories(eager_load=eager_load)
=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 2015-02-23 18:07:51 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2015-02-23 18:07:51 +0000
@@ -33,11 +33,21 @@
IGitRepository,
IGitRepositorySet,
)
-from lp.registry.enums import BranchSharingPolicy
+from lp.registry.enums import (
+ BranchSharingPolicy,
+ PersonVisibility,
+ TeamMembershipPolicy,
+ )
+from lp.registry.interfaces.accesspolicy import (
+ IAccessArtifactSource,
+ IAccessPolicyArtifactSource,
+ IAccessPolicySource,
+ )
from lp.registry.interfaces.persondistributionsourcepackage import (
IPersonDistributionSourcePackageFactory,
)
from lp.registry.interfaces.personproduct import IPersonProductFactory
+from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
from lp.services.database.constants import UTC_NOW
from lp.services.webapp.authorization import check_permission
from lp.testing import (
@@ -129,6 +139,15 @@
project.setDefaultGitRepository(repository)
self.assertGitIdentity(repository, project.name)
+ def test_git_identity_private_default_for_project(self):
+ # Private repositories also have a short lp: URL.
+ project = self.factory.makeProduct()
+ repository = self.factory.makeGitRepository(
+ target=project, information_type=InformationType.USERDATA)
+ with admin_logged_in():
+ project.setDefaultGitRepository(repository)
+ self.assertGitIdentity(repository, project.name)
+
def test_git_identity_default_for_package(self):
# If a repository is the default for a package, then its Git
# identity uses the path to that package.
@@ -293,6 +312,57 @@
self.assertEqual(namespace, repository.namespace)
+class TestGitRepositoryPrivacy(TestCaseWithFactory):
+ """Tests for Git repository privacy."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ # Use an admin user as we aren't checking edit permissions here.
+ super(TestGitRepositoryPrivacy, self).setUp("admin@xxxxxxxxxxxxx")
+
+ def test_personal_repositories_for_private_teams_are_private(self):
+ team = self.factory.makeTeam(
+ membership_policy=TeamMembershipPolicy.MODERATED,
+ visibility=PersonVisibility.PRIVATE)
+ repository = self.factory.makeGitRepository(owner=team, target=team)
+ self.assertTrue(repository.private)
+ self.assertEqual(
+ InformationType.PROPRIETARY, repository.information_type)
+
+ def test__reconcileAccess_for_project_repository(self):
+ # _reconcileAccess uses a project policy for a project repository.
+ repository = self.factory.makeGitRepository(
+ information_type=InformationType.USERDATA)
+ [artifact] = getUtility(IAccessArtifactSource).ensure([repository])
+ getUtility(IAccessPolicyArtifactSource).deleteByArtifact([artifact])
+ removeSecurityProxy(repository)._reconcileAccess()
+ self.assertContentEqual(
+ getUtility(IAccessPolicySource).find(
+ [(repository.target, InformationType.USERDATA)]),
+ get_policies_for_artifact(repository))
+
+ def test__reconcileAccess_for_package_repository(self):
+ # Git repository privacy isn't yet supported for distributions, so
+ # no AccessPolicyArtifact is created for a package repository.
+ repository = self.factory.makeGitRepository(
+ target=self.factory.makeDistributionSourcePackage(),
+ information_type=InformationType.USERDATA)
+ removeSecurityProxy(repository)._reconcileAccess()
+ self.assertEqual([], get_policies_for_artifact(repository))
+
+ def test__reconcileAccess_for_personal_repository(self):
+ # _reconcileAccess uses a person policy for a personal repository.
+ team_owner = self.factory.makeTeam()
+ repository = self.factory.makeGitRepository(
+ owner=team_owner, target=team_owner,
+ information_type=InformationType.USERDATA)
+ removeSecurityProxy(repository)._reconcileAccess()
+ self.assertContentEqual(
+ getUtility(IAccessPolicySource).findByTeam([team_owner]),
+ get_policies_for_artifact(repository))
+
+
class TestGitRepositoryGetAllowedInformationTypes(TestCaseWithFactory):
"""Test `IGitRepository.getAllowedInformationTypes`."""
@@ -345,6 +415,17 @@
self.assertFalse(
check_permission("launchpad.Moderate", repository))
+ def test_methods_smoketest(self):
+ # Users with launchpad.Moderate can call transitionToInformationType.
+ project = self.factory.makeProduct()
+ repository = self.factory.makeGitRepository(target=project)
+ with person_logged_in(project.owner):
+ project.setBranchSharingPolicy(BranchSharingPolicy.PUBLIC)
+ repository.transitionToInformationType(
+ InformationType.PRIVATESECURITY, project.owner)
+ self.assertEqual(
+ InformationType.PRIVATESECURITY, repository.information_type)
+
def test_attribute_smoketest(self):
# Users with launchpad.Moderate can set attributes.
project = self.factory.makeProduct()
@@ -477,6 +558,47 @@
repository.setTarget(target=owner, user=owner)
self.assertEqual(owner, repository.target)
+ def test_private_personal_forbidden_for_public_teams(self):
+ # Only private teams can have private personal repositories.
+ owner = self.factory.makeTeam()
+ repository = self.factory.makeGitRepository(
+ owner=owner, information_type=InformationType.USERDATA)
+ with admin_logged_in():
+ self.assertRaises(
+ GitTargetError, repository.setTarget, target=owner, user=owner)
+
+ def test_private_personal_allowed_for_private_teams(self):
+ # Only private teams can have private personal repositories.
+ owner = self.factory.makeTeam(visibility=PersonVisibility.PRIVATE)
+ with person_logged_in(owner):
+ repository = self.factory.makeGitRepository(
+ owner=owner, information_type=InformationType.USERDATA)
+ repository.setTarget(target=owner, user=owner)
+ self.assertEqual(owner, repository.target)
+
+ def test_reconciles_access(self):
+ # setTarget calls _reconcileAccess to make the sharing schema
+ # match the new target.
+ repository = self.factory.makeGitRepository(
+ information_type=InformationType.USERDATA)
+ new_project = self.factory.makeProduct()
+ with admin_logged_in():
+ repository.setTarget(target=new_project, user=repository.owner)
+ self.assertEqual(
+ new_project, get_policies_for_artifact(repository)[0].pillar)
+
+ def test_reconciles_access_personal(self):
+ # setTarget calls _reconcileAccess to make the sharing schema
+ # correct for a private personal repository.
+ owner = self.factory.makeTeam(visibility=PersonVisibility.PRIVATE)
+ with person_logged_in(owner):
+ repository = self.factory.makeGitRepository(
+ owner=owner, target=owner,
+ information_type=InformationType.USERDATA)
+ repository.setTarget(target=owner, user=owner)
+ self.assertEqual(
+ owner, get_policies_for_artifact(repository)[0].person)
+
def test_public_to_proprietary_only_project(self):
# A repository cannot be moved to a target where the sharing policy
# does not allow it.
@@ -513,3 +635,15 @@
person = self.factory.makePerson()
self.assertIsNone(
getUtility(IGitRepositorySet).getByPath(person, "nonexistent"))
+
+ def test_getByPath_inaccessible(self):
+ # If the given user cannot view the matched repository, then
+ # getByPath returns None.
+ owner = self.factory.makePerson()
+ repository = self.factory.makeGitRepository(
+ owner=owner, information_type=InformationType.USERDATA)
+ grs = getUtility(IGitRepositorySet)
+ with person_logged_in(owner):
+ path = repository.shortened_path
+ self.assertEqual(repository, grs.getByPath(owner, path))
+ self.assertIsNone(grs.getByPath(self.factory.makePerson(), path))
=== modified file 'lib/lp/registry/services/sharingservice.py'
--- lib/lp/registry/services/sharingservice.py 2015-02-16 13:01:34 +0000
+++ lib/lp/registry/services/sharingservice.py 2015-02-23 18:07:51 +0000
@@ -36,6 +36,7 @@
from lp.bugs.interfaces.bugtask import IBugTaskSet
from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
from lp.code.interfaces.branchcollection import IAllBranches
+from lp.code.interfaces.gitcollection import IAllGitRepositories
from lp.registry.enums import (
BranchSharingPolicy,
BugSharingPolicy,
@@ -227,7 +228,11 @@
branches = list(wanted_branches.getBranches())
# Load the Git repositories.
gitrepositories = []
- # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place.
+ if gitrepository_ids:
+ all_gitrepositories = getUtility(IAllGitRepositories)
+ wanted_gitrepositories = all_gitrepositories.visibleByUser(
+ user).withIds(*gitrepository_ids)
+ gitrepositories = list(wanted_gitrepositories.getRepositories())
specifications = []
if specification_ids:
specifications = load(Specification, specification_ids)
@@ -360,7 +365,12 @@
# Load the Git repositories.
visible_gitrepositories = []
- # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place.
+ if gitrepositories_by_id:
+ all_gitrepositories = getUtility(IAllGitRepositories)
+ wanted_gitrepositories = all_gitrepositories.visibleByUser(
+ person).withIds(*gitrepositories_by_id.keys())
+ visible_gitrepositories = list(
+ wanted_gitrepositories.getRepositories())
# Load the specifications.
visible_specs = []
@@ -411,7 +421,17 @@
# Load the Git repositories.
invisible_gitrepositories = []
- # XXX cjwatson 2015-02-16: Fill in once IGitCollection is in place.
+ if gitrepositories_by_id:
+ all_gitrepositories = getUtility(IAllGitRepositories)
+ visible_gitrepository_ids = all_gitrepositories.visibleByUser(
+ person).withIds(
+ *gitrepositories_by_id.keys()).getRepositoryIds()
+ invisible_gitrepository_ids = (
+ set(gitrepositories_by_id.keys()).difference(
+ visible_gitrepository_ids))
+ invisible_gitrepositories = [
+ gitrepositories_by_id[gitrepository_id]
+ for gitrepository_id in invisible_gitrepository_ids]
return invisible_bugs, invisible_branches, invisible_gitrepositories
=== modified file 'lib/lp/registry/services/tests/test_sharingservice.py'
--- lib/lp/registry/services/tests/test_sharingservice.py 2015-02-16 13:01:34 +0000
+++ lib/lp/registry/services/tests/test_sharingservice.py 2015-02-23 18:07:51 +0000
@@ -23,6 +23,7 @@
CodeReviewNotificationLevel,
)
from lp.code.interfaces.branch import IBranch
+from lp.code.interfaces.gitrepository import IGitRepository
from lp.registry.enums import (
BranchSharingPolicy,
BugSharingPolicy,
@@ -928,12 +929,14 @@
[InformationType.USERDATA])
def _assert_revokeAccessGrants(self, pillar, bugs, branches,
- specifications):
+ gitrepositories, specifications):
artifacts = []
if bugs:
artifacts.extend(bugs)
if branches:
artifacts.extend(branches)
+ if gitrepositories:
+ artifacts.extend(gitrepositories)
if specifications:
artifacts.extend(specifications)
policy = self.factory.makeAccessPolicy(pillar=pillar,
@@ -961,6 +964,8 @@
branch.subscribe(person,
BranchSubscriptionNotificationLevel.NOEMAIL, None,
CodeReviewNotificationLevel.NOEMAIL, pillar.owner)
+ # XXX cjwatson 2015-02-05: subscribe to Git repositories when
+ # implemented
for spec in specifications or []:
spec.subscribe(person)
@@ -973,7 +978,7 @@
self.service.revokeAccessGrants(
pillar, grantee, pillar.owner, bugs=bugs, branches=branches,
- specifications=specifications)
+ gitrepositories=gitrepositories, specifications=specifications)
with block_on_job(self):
transaction.commit()
@@ -987,18 +992,22 @@
self.assertNotIn(grantee, bug.getDirectSubscribers())
for branch in branches or []:
self.assertNotIn(grantee, branch.subscribers)
+ # XXX cjwatson 2015-02-05: check revocation of subscription to Git
+ # repositories when implemented
for spec in specifications or []:
self.assertNotIn(grantee, spec.subscribers)
- # Someone else still has access to the bugs and branches.
+ # Someone else still has access to the artifacts.
grants = accessartifact_grant_source.findByArtifact(
access_artifacts, [someone])
self.assertEqual(1, grants.count())
- # Someone else still has subscriptions to the bugs and branches.
+ # Someone else still has subscriptions to the artifacts.
for bug in bugs or []:
self.assertIn(someone, bug.getDirectSubscribers())
for branch in branches or []:
self.assertIn(someone, branch.subscribers)
+ # XXX cjwatson 2015-02-05: check subscription to Git repositories
+ # when implemented
for spec in specifications or []:
self.assertIn(someone, spec.subscribers)
@@ -1010,7 +1019,7 @@
bug = self.factory.makeBug(
target=distro, owner=owner,
information_type=InformationType.USERDATA)
- self._assert_revokeAccessGrants(distro, [bug], None, None)
+ self._assert_revokeAccessGrants(distro, [bug], None, None, None)
def test_revokeAccessGrantsBranches(self):
owner = self.factory.makePerson()
@@ -1019,7 +1028,17 @@
branch = self.factory.makeBranch(
product=product, owner=owner,
information_type=InformationType.USERDATA)
- self._assert_revokeAccessGrants(product, None, [branch], None)
+ self._assert_revokeAccessGrants(product, None, [branch], None, None)
+
+ def test_revokeAccessGrantsGitRepositories(self):
+ owner = self.factory.makePerson()
+ product = self.factory.makeProduct(owner=owner)
+ login_person(owner)
+ gitrepository = self.factory.makeGitRepository(
+ target=product, owner=owner,
+ information_type=InformationType.USERDATA)
+ self._assert_revokeAccessGrants(
+ product, None, None, [gitrepository], None)
def test_revokeAccessGrantsSpecifications(self):
owner = self.factory.makePerson()
@@ -1030,15 +1049,18 @@
specification = self.factory.makeSpecification(
product=product, owner=owner,
information_type=InformationType.EMBARGOED)
- self._assert_revokeAccessGrants(product, None, None, [specification])
+ self._assert_revokeAccessGrants(
+ product, None, None, None, [specification])
def _assert_revokeTeamAccessGrants(self, pillar, bugs, branches,
- specifications):
+ gitrepositories, specifications):
artifacts = []
if bugs:
artifacts.extend(bugs)
if branches:
artifacts.extend(branches)
+ if gitrepositories:
+ artifacts.extend(gitrepositories)
if specifications:
artifacts.extend(specifications)
policy = self.factory.makeAccessPolicy(pillar=pillar,
@@ -1065,6 +1087,8 @@
branch.subscribe(
person, BranchSubscriptionNotificationLevel.NOEMAIL,
None, CodeReviewNotificationLevel.NOEMAIL, pillar.owner)
+ # XXX cjwatson 2015-02-05: subscribe to Git repositories when
+ # implemented
# Subscribing somebody to a specification does not yet imply
# granting access to this person.
if specifications:
@@ -1075,12 +1099,16 @@
# Check that grantees have expected access grants and subscriptions.
for person in [team_grantee, person_grantee]:
- visible_bugs, visible_branches, _, visible_specs = (
+ (visible_bugs, visible_branches, visible_gitrepositories,
+ visible_specs) = (
self.service.getVisibleArtifacts(
person, bugs=bugs, branches=branches,
+ gitrepositories=gitrepositories,
specifications=specifications))
self.assertContentEqual(bugs or [], visible_bugs)
self.assertContentEqual(branches or [], visible_branches)
+ # XXX cjwatson 2015-02-05: check Git repositories when
+ # subscription is implemented
self.assertContentEqual(specifications or [], visible_specs)
for person in [team_grantee, person_grantee]:
for bug in bugs or []:
@@ -1088,7 +1116,7 @@
self.service.revokeAccessGrants(
pillar, team_grantee, pillar.owner, bugs=bugs, branches=branches,
- specifications=specifications)
+ gitrepositories=gitrepositories, specifications=specifications)
with block_on_job(self):
transaction.commit()
@@ -1103,11 +1131,14 @@
for person in [team_grantee, person_grantee]:
for bug in bugs or []:
self.assertNotIn(person, bug.getDirectSubscribers())
- visible_bugs, visible_branches, _, visible_specs = (
+ (visible_bugs, visible_branches, visible_gitrepositories,
+ visible_specs) = (
self.service.getVisibleArtifacts(
- person, bugs=bugs, branches=branches))
+ person, bugs=bugs, branches=branches,
+ gitrepositories=gitrepositories))
self.assertContentEqual([], visible_bugs)
self.assertContentEqual([], visible_branches)
+ self.assertContentEqual([], visible_gitrepositories)
self.assertContentEqual([], visible_specs)
def test_revokeTeamAccessGrantsBugs(self):
@@ -1118,7 +1149,7 @@
bug = self.factory.makeBug(
target=distro, owner=owner,
information_type=InformationType.USERDATA)
- self._assert_revokeTeamAccessGrants(distro, [bug], None, None)
+ self._assert_revokeTeamAccessGrants(distro, [bug], None, None, None)
def test_revokeTeamAccessGrantsBranches(self):
# Users with launchpad.Edit can delete all access for a grantee.
@@ -1127,7 +1158,20 @@
login_person(owner)
branch = self.factory.makeBranch(
owner=owner, information_type=InformationType.USERDATA)
- self._assert_revokeTeamAccessGrants(product, None, [branch], None)
+ self._assert_revokeTeamAccessGrants(
+ product, None, [branch], None, None)
+
+ # XXX cjwatson 2015-02-05: Enable this once GitRepositorySubscription is
+ # implemented.
+ def disabled_test_revokeTeamAccessGrantsGitRepositories(self):
+ # Users with launchpad.Edit can delete all access for a grantee.
+ owner = self.factory.makePerson()
+ product = self.factory.makeProduct(owner=owner)
+ login_person(owner)
+ gitrepository = self.factory.makeGitRepository(
+ owner=owner, information_type=InformationType.USERDATA)
+ self._assert_revokeTeamAccessGrants(
+ product, None, None, [gitrepository], None)
def test_revokeTeamAccessGrantsSpecifications(self):
# Users with launchpad.Edit can delete all access for a grantee.
@@ -1140,7 +1184,7 @@
product=product, owner=owner,
information_type=InformationType.EMBARGOED)
self._assert_revokeTeamAccessGrants(
- product, None, None, [specification])
+ product, None, None, None, [specification])
def _assert_revokeAccessGrantsUnauthorized(self):
# revokeAccessGrants raises an Unauthorized exception if the user
@@ -1163,9 +1207,9 @@
login_person(self.factory.makePerson())
self._assert_revokeAccessGrantsUnauthorized()
- def test_revokeAccessGrants_without_bugs_or_branches(self):
+ def test_revokeAccessGrants_without_artifacts(self):
# The revokeAccessGrants method raises a ValueError if called without
- # specifying either bugs or branches.
+ # specifying any artifacts.
owner = self.factory.makePerson()
product = self.factory.makeProduct(owner=owner)
grantee = self.factory.makePerson()
@@ -1174,24 +1218,27 @@
ValueError, self.service.revokeAccessGrants,
product, grantee, product.owner)
- def _assert_ensureAccessGrants(self, user, bugs, branches, specifications,
- grantee=None):
+ def _assert_ensureAccessGrants(self, user, bugs, branches, gitrepositories,
+ specifications, grantee=None):
# Creating access grants works as expected.
if not grantee:
grantee = self.factory.makePerson()
self.service.ensureAccessGrants(
[grantee], user, bugs=bugs, branches=branches,
- specifications=specifications)
+ gitrepositories=gitrepositories, specifications=specifications)
# Check that grantee has expected access grants.
shared_bugs = []
shared_branches = []
+ shared_gitrepositories = []
shared_specifications = []
all_pillars = []
for bug in bugs or []:
all_pillars.extend(bug.affected_pillars)
for branch in branches or []:
all_pillars.append(branch.target.context)
+ for gitrepository in gitrepositories or []:
+ all_pillars.append(gitrepository.target)
for specification in specifications or []:
all_pillars.append(specification.target)
policies = getUtility(IAccessPolicySource).findByPillar(all_pillars)
@@ -1203,10 +1250,13 @@
shared_bugs.append(a.concrete_artifact)
elif IBranch.providedBy(a.concrete_artifact):
shared_branches.append(a.concrete_artifact)
+ elif IGitRepository.providedBy(a.concrete_artifact):
+ shared_gitrepositories.append(a.concrete_artifact)
elif ISpecification.providedBy(a.concrete_artifact):
shared_specifications.append(a.concrete_artifact)
self.assertContentEqual(bugs or [], shared_bugs)
self.assertContentEqual(branches or [], shared_branches)
+ self.assertContentEqual(gitrepositories or [], shared_gitrepositories)
self.assertContentEqual(specifications or [], shared_specifications)
def test_ensureAccessGrantsBugs(self):
@@ -1217,7 +1267,7 @@
bug = self.factory.makeBug(
target=distro, owner=owner,
information_type=InformationType.USERDATA)
- self._assert_ensureAccessGrants(owner, [bug], None, None)
+ self._assert_ensureAccessGrants(owner, [bug], None, None, None)
def test_ensureAccessGrantsBranches(self):
# Access grants can be created for branches.
@@ -1227,7 +1277,18 @@
branch = self.factory.makeBranch(
product=product, owner=owner,
information_type=InformationType.USERDATA)
- self._assert_ensureAccessGrants(owner, None, [branch], None)
+ self._assert_ensureAccessGrants(owner, None, [branch], None, None)
+
+ def test_ensureAccessGrantsGitRepositories(self):
+ # Access grants can be created for Git repositories.
+ owner = self.factory.makePerson()
+ product = self.factory.makeProduct(owner=owner)
+ login_person(owner)
+ gitrepository = self.factory.makeGitRepository(
+ target=product, owner=owner,
+ information_type=InformationType.USERDATA)
+ self._assert_ensureAccessGrants(
+ owner, None, None, [gitrepository], None)
def test_ensureAccessGrantsSpecifications(self):
# Access grants can be created for branches.
@@ -1244,7 +1305,8 @@
with person_logged_in(owner):
specification.transitionToInformationType(
InformationType.PROPRIETARY, owner)
- self._assert_ensureAccessGrants(owner, None, None, [specification])
+ self._assert_ensureAccessGrants(
+ owner, None, None, None, [specification])
def test_ensureAccessGrantsExisting(self):
# Any existing access grants are retained and new ones created.
@@ -1263,7 +1325,7 @@
# Test with a new bug as well as the one for which access is already
# granted.
self._assert_ensureAccessGrants(
- owner, [bug, bug2], None, None, grantee)
+ owner, [bug, bug2], None, None, None, grantee)
def _assert_ensureAccessGrantsUnauthorized(self, user):
# ensureAccessGrants raises an Unauthorized exception if the user
@@ -1331,7 +1393,8 @@
self._assert_updatePillarSharingPoliciesUnauthorized(anyone)
def create_shared_artifacts(self, product, grantee, user):
- # Create some shared bugs and branches.
+ # Create some shared bugs, branches, Git repositories, and
+ # specifications.
bugs = []
bug_tasks = []
for x in range(0, 10):
@@ -1346,6 +1409,12 @@
product=product, owner=product.owner,
information_type=InformationType.USERDATA)
branches.append(branch)
+ gitrepositories = []
+ for x in range(0, 10):
+ gitrepository = self.factory.makeGitRepository(
+ target=product, owner=product.owner,
+ information_type=InformationType.USERDATA)
+ gitrepositories.append(gitrepository)
specs = []
for x in range(0, 10):
spec = self.factory.makeSpecification(
@@ -1371,9 +1440,11 @@
grant_access(bug, i == 9)
for i, branch in enumerate(branches):
grant_access(branch, i == 9)
+ for i, gitrepository in enumerate(gitrepositories):
+ grant_access(gitrepository, i == 9)
getUtility(IService, 'sharing').ensureAccessGrants(
[grantee], product.owner, specifications=specs[:9])
- return bug_tasks, branches, specs
+ return bug_tasks, branches, gitrepositories, specs
def test_getSharedArtifacts(self):
# Test the getSharedArtifacts method.
@@ -1384,14 +1455,16 @@
login_person(owner)
grantee = self.factory.makePerson()
user = self.factory.makePerson()
- bug_tasks, branches, specs = self.create_shared_artifacts(
- product, grantee, user)
+ bug_tasks, branches, gitrepositories, specs = (
+ self.create_shared_artifacts(product, grantee, user))
# Check the results.
- shared_bugtasks, shared_branches, _, shared_specs = (
+ (shared_bugtasks, shared_branches, shared_gitrepositories,
+ shared_specs) = (
self.service.getSharedArtifacts(product, grantee, user))
self.assertContentEqual(bug_tasks[:9], shared_bugtasks)
self.assertContentEqual(branches[:9], shared_branches)
+ self.assertContentEqual(gitrepositories[:9], shared_gitrepositories)
self.assertContentEqual(specs[:9], shared_specs)
def _assert_getSharedProjects(self, product, who=None):
@@ -1531,7 +1604,7 @@
login_person(owner)
grantee = self.factory.makePerson()
user = self.factory.makePerson()
- bug_tasks, ignored, ignored = self.create_shared_artifacts(
+ bug_tasks, _, _, _ = self.create_shared_artifacts(
product, grantee, user)
# Check the results.
@@ -1547,7 +1620,7 @@
login_person(owner)
grantee = self.factory.makePerson()
user = self.factory.makePerson()
- ignored, branches, ignored = self.create_shared_artifacts(
+ _, branches, _, _ = self.create_shared_artifacts(
product, grantee, user)
# Check the results.
@@ -1555,6 +1628,23 @@
product, grantee, user)
self.assertContentEqual(branches[:9], shared_branches)
+ def test_getSharedGitRepositories(self):
+ # Test the getSharedGitRepositories method.
+ owner = self.factory.makePerson()
+ product = self.factory.makeProduct(
+ owner=owner, specification_sharing_policy=(
+ SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY))
+ login_person(owner)
+ grantee = self.factory.makePerson()
+ user = self.factory.makePerson()
+ _, _, gitrepositories, _ = self.create_shared_artifacts(
+ product, grantee, user)
+
+ # Check the results.
+ shared_gitrepositories = self.service.getSharedGitRepositories(
+ product, grantee, user)
+ self.assertContentEqual(gitrepositories[:9], shared_gitrepositories)
+
def test_getSharedSpecifications(self):
# Test the getSharedSpecifications method.
owner = self.factory.makePerson()
@@ -1564,7 +1654,7 @@
login_person(owner)
grantee = self.factory.makePerson()
user = self.factory.makePerson()
- ignored, ignored, specifications = self.create_shared_artifacts(
+ _, _, _, specifications = self.create_shared_artifacts(
product, grantee, user)
# Check the results.
@@ -1592,6 +1682,16 @@
login_person(owner)
self._assert_getPeopleWithoutAccess(product, branch)
+ def test_getPeopleWithAccessGitRepositories(self):
+ # Test the getPeopleWithoutAccess method with Git repositories.
+ owner = self.factory.makePerson()
+ product = self.factory.makeProduct(owner=owner)
+ gitrepository = self.factory.makeGitRepository(
+ target=product, owner=owner,
+ information_type=InformationType.USERDATA)
+ login_person(owner)
+ self._assert_getPeopleWithoutAccess(product, gitrepository)
+
def _assert_getPeopleWithoutAccess(self, product, artifact):
access_artifact = self.factory.makeAccessArtifact(concrete=artifact)
# Make some people to check. people[:5] will not have access.
@@ -1647,6 +1747,12 @@
product=product, owner=owner,
information_type=InformationType.USERDATA)
branches.append(branch)
+ gitrepositories = []
+ for x in range(0, 10):
+ gitrepository = self.factory.makeGitRepository(
+ target=product, owner=owner,
+ information_type=InformationType.USERDATA)
+ gitrepositories.append(gitrepository)
specifications = []
for x in range(0, 10):
@@ -1662,46 +1768,58 @@
artifact=access_artifact, grantee=grantee, grantor=owner)
return access_artifact
- # Grant access to some of the bugs and branches.
+ # Grant access to some of the artifacts.
for bug in bugs[:5]:
grant_access(bug)
for branch in branches[:5]:
grant_access(branch)
+ for gitrepository in gitrepositories[:5]:
+ grant_access(gitrepository)
for spec in specifications[:5]:
grant_access(spec)
- return grantee, owner, branches, bugs, specifications
+ return grantee, owner, bugs, branches, gitrepositories, specifications
def test_getVisibleArtifacts(self):
# Test the getVisibleArtifacts method.
- grantee, ignore, branches, bugs, specs = self._make_Artifacts()
+ grantee, ignore, bugs, branches, gitrepositories, specs = (
+ self._make_Artifacts())
# Check the results.
- shared_bugs, shared_branches, _, shared_specs = (
+ shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (
self.service.getVisibleArtifacts(
- grantee, bugs=bugs, branches=branches, specifications=specs))
+ grantee, bugs=bugs, branches=branches,
+ gitrepositories=gitrepositories, specifications=specs))
self.assertContentEqual(bugs[:5], shared_bugs)
self.assertContentEqual(branches[:5], shared_branches)
+ self.assertContentEqual(gitrepositories[:5], shared_gitrepositories)
self.assertContentEqual(specs[:5], shared_specs)
def test_getVisibleArtifacts_grant_on_pillar(self):
# getVisibleArtifacts() returns private specifications if
# user has a policy grant for the pillar of the specification.
- ignore, owner, branches, bugs, specs = self._make_Artifacts()
- shared_bugs, shared_branches, _, shared_specs = (
+ _, owner, bugs, branches, gitrepositories, specs = (
+ self._make_Artifacts())
+ shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (
self.service.getVisibleArtifacts(
- owner, bugs=bugs, branches=branches, specifications=specs))
+ owner, bugs=bugs, branches=branches,
+ gitrepositories=gitrepositories, specifications=specs))
self.assertContentEqual(bugs, shared_bugs)
self.assertContentEqual(branches, shared_branches)
+ self.assertContentEqual(gitrepositories, shared_gitrepositories)
self.assertContentEqual(specs, shared_specs)
def test_getInvisibleArtifacts(self):
# Test the getInvisibleArtifacts method.
- grantee, ignore, branches, bugs, specs = self._make_Artifacts()
+ grantee, ignore, bugs, branches, gitrepositories, specs = (
+ self._make_Artifacts())
# Check the results.
- not_shared_bugs, not_shared_branches, _ = (
+ not_shared_bugs, not_shared_branches, not_shared_gitrepositories = (
self.service.getInvisibleArtifacts(
- grantee, bugs=bugs, branches=branches))
+ grantee, bugs=bugs, branches=branches,
+ gitrepositories=gitrepositories))
self.assertContentEqual(bugs[5:], not_shared_bugs)
self.assertContentEqual(branches[5:], not_shared_branches)
+ self.assertContentEqual(
+ gitrepositories[5:], not_shared_gitrepositories)
def _assert_getVisibleArtifacts_bug_change(self, change_callback):
# Test the getVisibleArtifacts method excludes bugs after a change of
@@ -1723,7 +1841,7 @@
information_type=InformationType.USERDATA)
bugs.append(bug)
- shared_bugs, shared_branches, _, shared_specs = (
+ shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (
self.service.getVisibleArtifacts(grantee, bugs=bugs))
self.assertContentEqual(bugs, shared_bugs)
@@ -1731,7 +1849,7 @@
for x in range(0, 5):
change_callback(bugs[x], owner)
# Check the results.
- shared_bugs, shared_branches, _, shared_specs = (
+ shared_bugs, shared_branches, shared_gitrepositories, shared_specs = (
self.service.getVisibleArtifacts(grantee, bugs=bugs))
self.assertContentEqual(bugs[5:], shared_bugs)
=== modified file 'lib/lp/registry/tests/test_accesspolicy.py'
--- lib/lp/registry/tests/test_accesspolicy.py 2013-06-20 05:50:00 +0000
+++ lib/lp/registry/tests/test_accesspolicy.py 2015-02-23 18:07:51 +0000
@@ -1,4 +1,4 @@
-# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2011-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -280,6 +280,13 @@
return self.factory.makeBranch()
+class TestAccessArtifactGitRepository(BaseAccessArtifactTests,
+ TestCaseWithFactory):
+
+ def getConcreteArtifact(self):
+ return self.factory.makeGitRepository()
+
+
class TestAccessArtifactBug(BaseAccessArtifactTests,
TestCaseWithFactory):
=== modified file 'lib/lp/registry/tests/test_sharingjob.py'
--- lib/lp/registry/tests/test_sharingjob.py 2014-06-19 06:38:53 +0000
+++ lib/lp/registry/tests/test_sharingjob.py 2015-02-23 18:07:51 +0000
@@ -1,4 +1,4 @@
-# Copyright 2012 Canonical Ltd. This software is licensed under the
+# Copyright 2012-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for SharingJobs."""
@@ -118,6 +118,16 @@
'for branch_ids=[%d], requestor=%s>'
% (branch.id, requestor.name), repr(job))
+ def test_repr_gitrepositories(self):
+ requestor = self.factory.makePerson()
+ gitrepository = self.factory.makeGitRepository()
+ job = getUtility(IRemoveArtifactSubscriptionsJobSource).create(
+ requestor, artifacts=[gitrepository])
+ self.assertEqual(
+ '<REMOVE_ARTIFACT_SUBSCRIPTIONS job reconciling subscriptions '
+ 'for gitrepository_ids=[%d], requestor=%s>'
+ % (gitrepository.id, requestor.name), repr(job))
+
def test_repr_specifications(self):
requestor = self.factory.makePerson()
specification = self.factory.makeSpecification()
@@ -303,6 +313,9 @@
branch = self.factory.makeBranch(
owner=owner, product=product,
information_type=InformationType.USERDATA)
+ gitrepository = self.factory.makeGitRepository(
+ owner=owner, target=product,
+ information_type=InformationType.USERDATA)
specification = self.factory.makeSpecification(
owner=owner, product=product,
information_type=InformationType.PROPRIETARY)
@@ -317,6 +330,8 @@
branch.subscribe(artifact_indirect_grantee,
BranchSubscriptionNotificationLevel.NOEMAIL, None,
CodeReviewNotificationLevel.NOEMAIL, owner)
+ # XXX cjwatson 2015-02-05: Fill this in once we have
+ # GitRepositorySubscription.
# Subscribing somebody to a specification does not automatically
# create an artifact grant.
spec_artifact = self.factory.makeAccessArtifact(specification)
@@ -325,10 +340,10 @@
with person_logged_in(product.owner):
specification.subscribe(artifact_indirect_grantee, owner)
- # pick one of the concrete artifacts (bug, branch or spec)
- # and subscribe the teams and persons.
+ # pick one of the concrete artifacts (bug, branch, Git repository,
+ # or spec) and subscribe the teams and persons.
concrete_artifact, get_pillars, get_subscribers = configure_test(
- bug, branch, specification, policy_team_grantee,
+ bug, branch, gitrepository, specification, policy_team_grantee,
policy_indirect_grantee, artifact_team_grantee, owner)
# Subscribing policy_team_grantee has created an artifact grant so we
@@ -361,6 +376,8 @@
self.assertIn(artifact_team_grantee, subscribers)
self.assertIn(artifact_indirect_grantee, bug.getDirectSubscribers())
self.assertIn(artifact_indirect_grantee, branch.subscribers)
+ # XXX cjwatson 2015-02-05: Fill this in once we have
+ # GitRepositorySubscription.
self.assertIn(artifact_indirect_grantee,
removeSecurityProxy(specification).subscribers)
@@ -373,9 +390,9 @@
return removeSecurityProxy(
concrete_artifact).getDirectSubscribers()
- def configure_test(bug, branch, specification, policy_team_grantee,
- policy_indirect_grantee, artifact_team_grantee,
- owner):
+ def configure_test(bug, branch, gitrepository, specification,
+ policy_team_grantee, policy_indirect_grantee,
+ artifact_team_grantee, owner):
concrete_artifact = bug
bug.subscribe(policy_team_grantee, owner)
bug.subscribe(policy_indirect_grantee, owner)
@@ -393,9 +410,9 @@
def get_subscribers(concrete_artifact):
return concrete_artifact.subscribers
- def configure_test(bug, branch, specification, policy_team_grantee,
- policy_indirect_grantee, artifact_team_grantee,
- owner):
+ def configure_test(bug, branch, gitrepository, specification,
+ policy_team_grantee, policy_indirect_grantee,
+ artifact_team_grantee, owner):
concrete_artifact = branch
branch.subscribe(
policy_team_grantee,
@@ -414,6 +431,27 @@
self._assert_artifact_change_unsubscribes(
change_callback, configure_test)
+ def _assert_gitrepository_change_unsubscribes(self, change_callback):
+
+ def get_pillars(concrete_artifact):
+ return [concrete_artifact.product]
+
+ def get_subscribers(concrete_artifact):
+ return concrete_artifact.subscribers
+
+ def configure_test(bug, branch, gitrepository, specification,
+ policy_team_grantee, policy_indirect_grantee,
+ artifact_team_grantee, owner):
+ concrete_artifact = gitrepository
+ # XXX cjwatson 2015-02-05: Fill this in once we have
+ # GitRepositorySubscription.
+ return concrete_artifact, get_pillars, get_subscribers
+
+ # XXX cjwatson 2015-02-05: Uncomment once we have
+ # GitRepositorySubscription.
+ #self._assert_artifact_change_unsubscribes(
+ # change_callback, configure_test)
+
def _assert_specification_change_unsubscribes(self, change_callback):
def get_pillars(concrete_artifact):
@@ -422,9 +460,9 @@
def get_subscribers(concrete_artifact):
return concrete_artifact.subscribers
- def configure_test(bug, branch, specification, policy_team_grantee,
- policy_indirect_grantee, artifact_team_grantee,
- owner):
+ def configure_test(bug, branch, gitrepository, specification,
+ policy_team_grantee, policy_indirect_grantee,
+ artifact_team_grantee, owner):
naked_spec = removeSecurityProxy(specification)
naked_spec.subscribe(policy_team_grantee, owner)
naked_spec.subscribe(policy_indirect_grantee, owner)
@@ -444,6 +482,13 @@
self._assert_branch_change_unsubscribes(change_information_type)
+ def test_change_information_type_gitrepository(self):
+ def change_information_type(gitrepository):
+ removeSecurityProxy(gitrepository).information_type = (
+ InformationType.PRIVATESECURITY)
+
+ self._assert_gitrepository_change_unsubscribes(change_information_type)
+
def test_change_information_type_specification(self):
def change_information_type(specification):
removeSecurityProxy(specification).information_type = (
=== modified file 'lib/lp/security.py'
--- lib/lp/security.py 2015-02-23 03:22:37 +0000
+++ lib/lp/security.py 2015-02-23 18:07:51 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Security policies for using content objects."""
@@ -83,6 +83,7 @@
)
from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
from lp.code.interfaces.diff import IPreviewDiff
+from lp.code.interfaces.gitcollection import IGitCollection
from lp.code.interfaces.gitrepository import (
IGitRepository,
user_has_special_git_repository_access,
@@ -1020,6 +1021,12 @@
if not mp.is_empty():
return True
+ # Grant visibility to people who can see Git repositories owned
+ # by the private team.
+ team_repositories = IGitCollection(self.obj)
+ if not team_repositories.visibleByUser(user.person).is_empty():
+ return True
+
# Grant visibility to users in a team that has the private team as
# a member, so that they can see the team properly in member
# listings.
Follow ups