launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #13571
[Merge] lp:~abentley/launchpad/projectgroup-private-projects into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/projectgroup-private-projects into lp:launchpad.
Commit message:
Fix milestone and projectgroup privacy bugs.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1063291 in Launchpad itself: "Project groups are broken by private projects"
https://bugs.launchpad.net/launchpad/+bug/1063291
For more details, see:
https://code.launchpad.net/~abentley/launchpad/projectgroup-private-projects/+merge/130590
= Summary =
Fix bug #1063291: Project groups are broken by private projects
== Proposed fix ==
Change Milestone.specifications to privacy-aware getSpecifications.
Change bugtasksearch._build_query to make Milestone.bugtasks privacy-aware.
Change ProjectGroup.milestones and all_milestones to be privacy-aware.
== Pre-implementation notes ==
Had a call with deryck to discuss test_getSpecifications_milestone_privacy. We concluded it was correct, and I summarized our discussion in the comment.
== LOC Rationale ==
part of Private Projects
== Implementation details ==
ProjectGroup.milestones and all_milestones are exported in the web service, so they could not be converted into user-taking function.
Deletion code currently only considers items the user can see. We expect that deletion may oops in cases where, e.g. a milestone has a proprietary blueprint assigned, but will fix in a follow-up.
== Tests ==
bin/test -t test_getSpecifications_milestone_privacy -t test_bugtasks_milestone_privacy -t test_getProducts_with_proprietary -t test_milestones_privacy -t test_all_milestones_privacy
== Demo and Q/A ==
- Add a public and proprietary project to a project group
- Add two milestones to the proprietary project.
- Add one milestone to the public project with the same name as one of the proprietary projects.
- Add public specifications and bug tasks to all the milestones
- Log in as an unprivileged user. You should see only one milestone, and on that milestone, only the bug tasks and milestones for the public project.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/bugs/model/bugtasksearch.py
lib/lp/registry/doc/milestone.txt
lib/lp/registry/model/projectgroup.py
lib/lp/registry/model/milestonetag.py
lib/lp/registry/tests/test_project_milestone.py
lib/lp/registry/configure.zcml
lib/lp/registry/tests/test_projectgroup.py
lib/lp/registry/interfaces/milestone.py
lib/lp/registry/browser/__init__.py
lib/lp/registry/tests/test_milestone.py
lib/lp/registry/browser/tests/test_projectgroup.py
lib/lp/registry/tests/test_milestonetag.py
lib/lp/registry/model/milestone.py
lib/lp/registry/browser/milestone.py
--
https://code.launchpad.net/~abentley/launchpad/projectgroup-private-projects/+merge/130590
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/projectgroup-private-projects into lp:launchpad.
=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
--- lib/lp/bugs/model/bugtasksearch.py 2012-10-18 04:26:34 +0000
+++ lib/lp/bugs/model/bugtasksearch.py 2012-10-19 15:54:26 +0000
@@ -80,7 +80,10 @@
from lp.registry.model.milestone import Milestone
from lp.registry.model.milestonetag import MilestoneTag
from lp.registry.model.person import Person
-from lp.registry.model.product import Product
+from lp.registry.model.product import (
+ Product,
+ ProductSet,
+ )
from lp.registry.model.teammembership import TeamParticipation
from lp.services.database.bulk import load
from lp.services.database.decoratedresultset import DecoratedResultSet
@@ -387,7 +390,8 @@
where=And(
Product.project == params.milestone.target,
Milestone.productID == Product.id,
- Milestone.name == params.milestone.name))))
+ Milestone.name == params.milestone.name,
+ ProductSet.getProductPrivacyFilter(params.user)))))
else:
extra_clauses.append(
search_value_to_storm_where_condition(
=== modified file 'lib/lp/registry/browser/__init__.py'
--- lib/lp/registry/browser/__init__.py 2012-09-27 19:11:59 +0000
+++ lib/lp/registry/browser/__init__.py 2012-10-19 15:54:26 +0000
@@ -34,6 +34,7 @@
from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
from lp.registry.interfaces.productseries import IProductSeries
from lp.registry.interfaces.series import SeriesStatus
+from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.publisher import (
canonical_url,
DataDownloadView,
@@ -175,7 +176,8 @@
if IProductSeries.providedBy(target):
return list(target._all_specifications)
else:
- return list(target.specifications)
+ user = getUtility(ILaunchBag).user
+ return list(target.getSpecifications(user))
def _getProductRelease(self, milestone):
"""The `IProductRelease` associated with the milestone."""
=== modified file 'lib/lp/registry/browser/milestone.py'
--- lib/lp/registry/browser/milestone.py 2012-10-04 20:38:22 +0000
+++ lib/lp/registry/browser/milestone.py 2012-10-19 15:54:26 +0000
@@ -214,7 +214,7 @@
@cachedproperty
def specifications(self):
"""The list of specifications targeted to this milestone."""
- return list(self.context.specifications)
+ return list(self.context.getSpecifications(self.user))
@cachedproperty
def _bugtasks(self):
=== modified file 'lib/lp/registry/browser/tests/test_projectgroup.py'
--- lib/lp/registry/browser/tests/test_projectgroup.py 2012-10-05 00:00:15 +0000
+++ lib/lp/registry/browser/tests/test_projectgroup.py 2012-10-19 15:54:26 +0000
@@ -22,6 +22,7 @@
from lp.services.webapp import canonical_url
from lp.services.webapp.interfaces import ILaunchBag
from lp.testing import (
+ BrowserTestCase,
celebrity_logged_in,
person_logged_in,
TestCaseWithFactory,
@@ -32,7 +33,7 @@
from lp.testing.views import create_initialized_view
-class TestProjectGroupView(TestCaseWithFactory):
+class TestProjectGroupView(BrowserTestCase):
"""Tests the +index view."""
layer = DatabaseFunctionalLayer
@@ -53,6 +54,48 @@
team_membership_policy_data,
cache.objects['team_membership_policy_data'])
+ def test_proprietary_product(self):
+ # Proprietary projects are not listed for people without access to
+ # them.
+ owner = self.factory.makePerson()
+ product = self.factory.makeProduct(
+ information_type=InformationType.PROPRIETARY,
+ project=self.project_group, owner=owner)
+ owner_browser = self.getViewBrowser(self.project_group,
+ user=owner)
+ with person_logged_in(owner):
+ product_name = product.name
+ self.assertIn(product_name, owner_browser.contents)
+ browser = self.getViewBrowser(self.project_group)
+ self.assertNotIn(product_name, browser.contents)
+
+ def test_proprietary_product_milestone(self):
+ # Proprietary projects are not listed for people without access to
+ # them.
+ owner = self.factory.makePerson()
+ public_product = self.factory.makeProduct(
+ information_type=InformationType.PUBLIC,
+ project=self.project_group, owner=owner)
+ public_milestone = self.factory.makeMilestone(product=public_product)
+ product = self.factory.makeProduct(
+ information_type=InformationType.PROPRIETARY,
+ project=self.project_group, owner=owner)
+ milestone = self.factory.makeMilestone(product=product,
+ name=public_milestone.name)
+ (group_milestone,) = self.project_group.milestones
+ self.factory.makeSpecification(milestone=public_milestone)
+ with person_logged_in(owner):
+ self.factory.makeSpecification(milestone=milestone)
+ product_name = product.displayname
+ with person_logged_in(None):
+ owner_browser = self.getViewBrowser(group_milestone, user=owner)
+ browser = self.getViewBrowser(group_milestone)
+
+ self.assertIn(product_name, owner_browser.contents)
+ self.assertIn(public_product.displayname, owner_browser.contents)
+ self.assertNotIn(product_name, browser.contents)
+ self.assertIn(public_product.displayname, browser.contents)
+
class TestProjectGroupEditView(TestCaseWithFactory):
"""Tests the edit view."""
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2012-10-18 14:09:19 +0000
+++ lib/lp/registry/configure.zcml 2012-10-19 15:54:26 +0000
@@ -1038,6 +1038,7 @@
displayname
distribution
distroseries
+ getSpecifications
getTags
getTagsData
name
@@ -1045,7 +1046,6 @@
product_release
productseries
series_target
- specifications
summary
target
title
=== modified file 'lib/lp/registry/doc/milestone.txt'
--- lib/lp/registry/doc/milestone.txt 2012-09-27 14:25:32 +0000
+++ lib/lp/registry/doc/milestone.txt 2012-10-19 15:54:26 +0000
@@ -296,7 +296,7 @@
netapplet []
gnomebaker []
- >>> print list(gnome.getMilestone('1.1').specifications)
+ >>> print list(gnome.getMilestone('1.1').getSpecifications(None))
[]
When a specification for a product is created and assigned to a product
@@ -306,7 +306,8 @@
>>> [spec.name for spec in applets._all_specifications]
[u'applets-specification']
- >>> [spec.name for spec in gnome.getMilestone('1.1').specifications]
+ >>> specs = gnome.getMilestone('1.1').getSpecifications(None)
+ >>> [spec.name for spec in specs]
[u'applets-specification']
=== modified file 'lib/lp/registry/interfaces/milestone.py'
--- lib/lp/registry/interfaces/milestone.py 2012-10-16 14:47:54 +0000
+++ lib/lp/registry/interfaces/milestone.py 2012-10-19 15:54:26 +0000
@@ -123,8 +123,6 @@
"The product, distribution, or project group for this "
"milestone."),
required=False))
- specifications = Attribute(
- "A list of specifications targeted to this object.")
dateexpected = exported(
FormattableDate(title=_("Date Targeted"), required=False,
description=_("Example: 2005-11-24")),
@@ -143,6 +141,9 @@
def bugtasks(user):
"""Get a list of non-conjoined bugtasks visible to this user."""
+ def getSpecifications(user):
+ """Return the specifications visible to this user."""
+
class IAbstractMilestone(IMilestoneData):
"""An intermediate interface for milestone, or a targeting point for bugs
=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py 2012-10-17 20:18:20 +0000
+++ lib/lp/registry/model/milestone.py 2012-10-19 15:54:26 +0000
@@ -64,6 +64,7 @@
from lp.services.database.lpstorm import IStore
from lp.services.database.sqlbase import SQLBase
from lp.services.propertycache import get_property_cache
+from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.sorting import expand_numbers
@@ -147,15 +148,15 @@
def title(self):
raise NotImplementedError
- @property
- def specifications(self):
+ def getSpecifications(self, user):
+ """See `IMilestoneData`"""
from lp.registry.model.person import Person
store = Store.of(self.target)
origin = [
Specification,
LeftJoin(Person, Specification.assigneeID == Person.id),
]
- milestones = self._milestone_ids_expr
+ milestones = self._milestone_ids_expr(user)
results = store.using(*origin).find(
(Specification, Person),
@@ -219,8 +220,7 @@
summary = StringCol(notNull=False, default=None)
code_name = StringCol(dbName='codename', notNull=False, default=None)
- @property
- def _milestone_ids_expr(self):
+ def _milestone_ids_expr(self, user):
return (self.id,)
@property
@@ -294,13 +294,14 @@
params = BugTaskSearchParams(milestone=self, user=None)
bugtasks = getUtility(IBugTaskSet).search(params)
subscriptions = IResultSet(self.getSubscriptions())
+ user = getUtility(ILaunchBag).user
assert subscriptions.is_empty(), (
"You cannot delete a milestone which has structural "
"subscriptions.")
assert bugtasks.count() == 0, (
"You cannot delete a milestone which has bugtasks targeted "
"to it.")
- assert self.specifications.count() == 0, (
+ assert self.getSpecifications(user).count() == 0, (
"You cannot delete a milestone which has specifications targeted "
"to it.")
assert self.product_release is None, (
@@ -440,16 +441,19 @@
self.series_target = None
self.summary = None
- @property
- def _milestone_ids_expr(self):
- from lp.registry.model.product import Product
+ def _milestone_ids_expr(self, user):
+ from lp.registry.model.product import (
+ Product,
+ ProductSet,
+ )
return Select(
Milestone.id,
tables=[Milestone, Product],
where=And(
Milestone.name == self.name,
Milestone.productID == Product.id,
- Product.project == self.target))
+ Product.project == self.target,
+ ProductSet.getProductPrivacyFilter(user)))
@property
def displayname(self):
=== modified file 'lib/lp/registry/model/milestonetag.py'
--- lib/lp/registry/model/milestonetag.py 2012-10-02 03:31:03 +0000
+++ lib/lp/registry/model/milestonetag.py 2012-10-19 15:54:26 +0000
@@ -77,8 +77,7 @@
"""See IMilestoneData."""
return self.displayname
- @property
- def _milestone_ids_expr(self):
+ def _milestone_ids_expr(self, user):
tag_constraints = And(*[
Exists(
Select(
=== modified file 'lib/lp/registry/model/projectgroup.py'
--- lib/lp/registry/model/projectgroup.py 2012-10-16 16:03:38 +0000
+++ lib/lp/registry/model/projectgroup.py 2012-10-19 15:54:26 +0000
@@ -88,7 +88,10 @@
ProjectMilestone,
)
from lp.registry.model.pillar import HasAliasMixin
-from lp.registry.model.product import Product
+from lp.registry.model.product import (
+ Product,
+ ProductSet,
+ )
from lp.registry.model.productseries import ProductSeries
from lp.services.database.constants import UTC_NOW
from lp.services.database.datetimecol import UtcDateTimeCol
@@ -101,6 +104,7 @@
from lp.services.helpers import shortlist
from lp.services.propertycache import cachedproperty
from lp.services.webapp.authorization import check_permission
+from lp.services.webapp.interfaces import ILaunchBag
from lp.services.worlddata.model.language import Language
from lp.translations.enums import TranslationPermission
from lp.translations.model.potemplate import POTemplate
@@ -170,13 +174,15 @@
"""See `IPillar`."""
return "Project Group"
- def getProducts(self):
- return Product.selectBy(
- project=self, active=True, orderBy='displayname')
+ def getProducts(self, user):
+ results = Store.of(self).find(
+ Product, Product.project == self, Product.active == True,
+ ProductSet.getProductPrivacyFilter(user))
+ return results.order_by(Product.displayname)
@cachedproperty
def products(self):
- return list(self.getProducts())
+ return list(self.getProducts(getUtility(ILaunchBag).user))
def getProduct(self, name):
return Product.selectOneBy(project=self, name=name)
@@ -404,7 +410,7 @@
return And(Milestone.productID == Product.id,
Product.projectID == self.id)
- def _getMilestones(self, only_active):
+ def _getMilestones(self, user, only_active):
"""Return a list of milestones for this project group.
If only_active is True, only active milestones are returned,
@@ -422,7 +428,8 @@
)
conditions = And(Milestone.product == Product.id,
Product.project == self,
- Product.active == True)
+ Product.active == True,
+ ProductSet.getProductPrivacyFilter(user))
result = store.find(columns, conditions)
result.group_by(Milestone.name)
if only_active:
@@ -466,7 +473,8 @@
@property
def milestones(self):
"""See `IProjectGroup`."""
- return self._getMilestones(only_active=True)
+ user = getUtility(ILaunchBag).user
+ return self._getMilestones(user, only_active=True)
@property
def product_milestones(self):
@@ -478,7 +486,8 @@
@property
def all_milestones(self):
"""See `IProjectGroup`."""
- return self._getMilestones(only_active=False)
+ user = getUtility(ILaunchBag).user
+ return self._getMilestones(user, only_active=False)
def getMilestone(self, name):
"""See `IProjectGroup`."""
=== modified file 'lib/lp/registry/tests/test_milestone.py'
--- lib/lp/registry/tests/test_milestone.py 2012-10-18 16:56:54 +0000
+++ lib/lp/registry/tests/test_milestone.py 2012-10-19 15:54:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Milestone related test helper."""
@@ -15,13 +15,15 @@
getChecker,
)
from zope.security.interfaces import Unauthorized
-from zope.security.proxy import removeSecurityProxy
from lp.app.enums import InformationType
from lp.app.errors import NotFoundError
from lp.app.interfaces.informationtype import IInformationType
from lp.app.interfaces.services import IService
-from lp.registry.enums import SharingPermission
+from lp.registry.enums import (
+ BugSharingPolicy,
+ SharingPermission,
+ )
from lp.registry.interfaces.distribution import IDistributionSet
from lp.registry.interfaces.milestone import (
IHasMilestones,
@@ -138,14 +140,14 @@
'active', 'bug_subscriptions', 'bugtasks', 'code_name',
'dateexpected', 'displayname', 'distribution', 'distroseries',
'_getOfficialTagClause', 'getBugSummaryContextWhereClause',
- 'getBugTaskWeightFunction', 'getSubscription',
- 'getSubscriptions', 'getTags', 'getTagsData',
+ 'getBugTaskWeightFunction', 'getSpecifications',
+ 'getSubscription', 'getSubscriptions', 'getTags', 'getTagsData',
'getUsedBugTagsWithOpenCounts', 'name', 'official_bug_tags',
'parent_subscription_target', 'product', 'product_release',
'productseries', 'searchTasks', 'series_target',
- 'specifications', 'summary', 'target', 'target_type_display',
- 'title', 'userCanAlterBugSubscription',
- 'userCanAlterSubscription', 'userHasBugSubscriptions',
+ 'summary', 'target', 'target_type_display', 'title',
+ 'userCanAlterBugSubscription', 'userCanAlterSubscription',
+ 'userHasBugSubscriptions',
)),
'launchpad.AnyAllowedPerson': set((
'addBugSubscription', 'addBugSubscriptionFilter',
@@ -423,7 +425,8 @@
owner=self.owner,
product=self.product,
)
- self.assertContentEqual(specifications, self.milestone.specifications)
+ self.assertContentEqual(specifications,
+ self.milestone.getSpecifications(None))
class MilestonesContainsPartialSpecifications(TestCaseWithFactory):
@@ -452,12 +455,14 @@
def test_milestones_on_product(self):
spec, target_milestone = self._create_milestones_on_target(
product=self.factory.makeProduct())
- self.assertContentEqual([spec], target_milestone.specifications)
+ self.assertContentEqual([spec],
+ target_milestone.getSpecifications(None))
def test_milestones_on_distribution(self):
spec, target_milestone = self._create_milestones_on_target(
distribution=self.factory.makeDistribution())
- self.assertContentEqual([spec], target_milestone.specifications)
+ self.assertContentEqual([spec],
+ target_milestone.getSpecifications(None))
def test_milestones_on_project(self):
# A Project (Project Group) milestone contains all specifications
@@ -468,7 +473,53 @@
spec, target_milestone = self._create_milestones_on_target(
product=product)
milestone = projectgroup.getMilestone(name=target_milestone.name)
- self.assertContentEqual([spec], milestone.specifications)
+ self.assertContentEqual([spec], milestone.getSpecifications(None))
+
+ def makeMixedMilestone(self):
+ projectgroup = self.factory.makeProject()
+ owner = self.factory.makePerson()
+ public_product = self.factory.makeProduct(project=projectgroup)
+ public_milestone = self.factory.makeMilestone(product=public_product)
+ product = self.factory.makeProduct(
+ owner=owner, information_type=InformationType.PROPRIETARY,
+ project=projectgroup, bug_sharing_policy=BugSharingPolicy.PUBLIC)
+ target_milestone = self.factory.makeMilestone(
+ product=product, name=public_milestone.name)
+ milestone = projectgroup.getMilestone(name=public_milestone.name)
+ return milestone, target_milestone, owner
+
+ def test_getSpecifications_milestone_privacy(self):
+ # Ensure getSpecifications respects milestone privacy.
+ # This looks wrong, because the specification is actually public, and
+ # we don't normally hide specifications based on the visibility of
+ # their products. But we're not trying to hide the specification.
+ # We're hiding the fact that this specification is associated with
+ # a proprietary Product milestone. We create a proprietary product
+ # because that's the only way to get a proprietary milestone.
+ milestone, target_milestone, owner = self.makeMixedMilestone()
+ with person_logged_in(owner):
+ spec = self.factory.makeSpecification(milestone=target_milestone)
+ self.assertContentEqual([],
+ milestone.getSpecifications(None))
+ self.assertContentEqual([spec],
+ milestone.getSpecifications(owner))
+
+ def test_bugtasks_milestone_privacy(self):
+ # Ensure bugtasks respects milestone privacy.
+ # This looks wrong, because the bugtask is actually public, and we
+ # don't normally hide bugtasks based on the visibility of their
+ # products. But we're not trying to hide the bugtask. We're hiding
+ # the fact that this bugtask is associated with a proprietary Product
+ # milestone. We create a proprietary product because that's the only
+ # way to get a proprietary milestone.
+ milestone, target_milestone, owner = self.makeMixedMilestone()
+ with person_logged_in(owner):
+ bugtask = self.factory.makeBugTask(
+ target=target_milestone.product)
+ with person_logged_in(bugtask.owner):
+ bugtask.transitionToMilestone(target_milestone, owner)
+ self.assertContentEqual([], milestone.bugtasks(None))
+ self.assertContentEqual([bugtask], milestone.bugtasks(owner))
def test_milestones_with_deleted_workitems(self):
# Deleted work items do not cause the specification to show up
@@ -479,7 +530,7 @@
product=milestone.product)
self.factory.makeSpecificationWorkItem(
specification=specification, milestone=milestone, deleted=True)
- self.assertContentEqual([], milestone.specifications)
+ self.assertContentEqual([], milestone.getSpecifications(None))
class TestMilestoneInformationType(TestCaseWithFactory):
=== modified file 'lib/lp/registry/tests/test_milestonetag.py'
--- lib/lp/registry/tests/test_milestonetag.py 2012-02-28 04:24:19 +0000
+++ lib/lp/registry/tests/test_milestonetag.py 2012-10-19 15:54:26 +0000
@@ -189,21 +189,21 @@
# Ensure that all specifications on a milestone can be retrieved.
specs, milestonetag = self._create_items_for_retrieval(
self._create_specifications)
- self.assertContentEqual(specs, milestonetag.specifications)
+ self.assertContentEqual(specs, milestonetag.getSpecifications(None))
def test_specifications_for_untagged_milestone(self):
# Ensure that specifications for a project group are retrieved
# only if associated with milestones having specified tags.
specs, milestonetag = self._create_items_for_untagged_milestone(
self._create_specifications)
- self.assertContentEqual(specs, milestonetag.specifications)
+ self.assertContentEqual(specs, milestonetag.getSpecifications(None))
def test_specifications_multiple_tags(self):
# Ensure that, in presence of multiple tags, only specifications
# for milestones associated with all the tags are retrieved.
specs, milestonetag = self._create_items_for_multiple_tags(
self._create_specifications)
- self.assertContentEqual(specs, milestonetag.specifications)
+ self.assertContentEqual(specs, milestonetag.getSpecifications(None))
class MilestoneTagWebServiceTest(WebServiceTestCase):
=== modified file 'lib/lp/registry/tests/test_project_milestone.py'
--- lib/lp/registry/tests/test_project_milestone.py 2012-08-07 02:31:56 +0000
+++ lib/lp/registry/tests/test_project_milestone.py 2012-10-19 15:54:26 +0000
@@ -227,7 +227,7 @@
# The spec for firefox (not a gnome product) is not included
# in the specifications, while the other two specs are included.
self.assertEqual(
- [spec.name for spec in gnome_milestone.specifications],
+ [spec.name for spec in gnome_milestone.getSpecifications(None)],
['evolution-specification', 'gnomebaker-specification'])
def _createProductBugtask(self, product_name, milestone_name):
=== modified file 'lib/lp/registry/tests/test_projectgroup.py'
--- lib/lp/registry/tests/test_projectgroup.py 2012-08-13 21:04:17 +0000
+++ lib/lp/registry/tests/test_projectgroup.py 2012-10-19 15:54:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -6,7 +6,9 @@
from lazr.restfulclient.errors import ClientError
from zope.component import getUtility
from zope.security.interfaces import Unauthorized
+from zope.security.proxy import removeSecurityProxy
+from lp.app.enums import InformationType
from lp.registry.enums import (
EXCLUSIVE_TEAM_POLICY,
INCLUSIVE_TEAM_POLICY,
@@ -17,6 +19,7 @@
launchpadlib_for,
login_celebrity,
login_person,
+ person_logged_in,
TestCaseWithFactory,
)
from lp.testing.layers import (
@@ -49,6 +52,19 @@
closed_team = self.factory.makeTeam(membership_policy=policy)
self.factory.makeProject(owner=closed_team)
+ def test_getProducts_with_proprietary(self):
+ # Proprietary projects are not listed for users without access to
+ # them.
+ project_group = removeSecurityProxy(self.factory.makeProject())
+ owner = self.factory.makePerson()
+ product = self.factory.makeProduct(
+ project=project_group, owner=owner,
+ information_type=InformationType.PROPRIETARY)
+ self.assertNotIn(product, project_group.getProducts(None))
+ outsider = self.factory.makePerson()
+ self.assertNotIn(product, project_group.getProducts(outsider))
+ self.assertIn(product, project_group.getProducts(owner))
+
class ProjectGroupSearchTestCase(TestCaseWithFactory):
@@ -163,6 +179,37 @@
self.pg.owner = self.factory.makePerson(name='project-group-owner')
+class TestMilestones(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_milestones_privacy(self):
+ """ProjectGroup.milestones uses logged-in user."""
+ owner = self.factory.makePerson()
+ project_group = self.factory.makeProject()
+ product = self.factory.makeProduct(
+ information_type=InformationType.PROPRIETARY, owner=owner,
+ project=project_group)
+ milestone = self.factory.makeMilestone(product=product)
+ self.assertContentEqual([], project_group.milestones)
+ with person_logged_in(owner):
+ names = [ms.name for ms in project_group.milestones]
+ self.assertEqual([milestone.name], names)
+
+ def test_all_milestones_privacy(self):
+ """ProjectGroup.milestones uses logged-in user."""
+ owner = self.factory.makePerson()
+ project_group = self.factory.makeProject()
+ product = self.factory.makeProduct(
+ information_type=InformationType.PROPRIETARY, owner=owner,
+ project=project_group)
+ milestone = self.factory.makeMilestone(product=product)
+ self.assertContentEqual([], project_group.milestones)
+ with person_logged_in(owner):
+ names = [ms.name for ms in project_group.all_milestones]
+ self.assertEqual([milestone.name], names)
+
+
class TestLaunchpadlibAPI(TestCaseWithFactory):
layer = DatabaseFunctionalLayer
Follow ups