Merge lp:~wgrant/launchpad/branch-type-policy-model into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: 15629
Proposed branch: lp:~wgrant/launchpad/branch-type-policy-model
Merge into: lp:launchpad
Prerequisite: lp:~wgrant/launchpad/branch-type-policy-db
Diff against target: 563 lines (+315/-6)
11 files modified
lib/lp/code/model/branchnamespace.py (+64/-0)
lib/lp/code/model/tests/test_branchnamespace.py (+92/-3)
lib/lp/registry/browser/product.py (+6/-0)
lib/lp/registry/configure.zcml (+1/-1)
lib/lp/registry/enums.py (+61/-0)
lib/lp/registry/interfaces/product.py (+15/-0)
lib/lp/registry/interfaces/sharingservice.py (+7/-0)
lib/lp/registry/model/product.py (+9/-1)
lib/lp/registry/services/sharingservice.py (+19/-0)
lib/lp/registry/services/tests/test_sharingservice.py (+34/-1)
lib/lp/services/features/flags.py (+7/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/branch-type-policy-model
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+114805@code.launchpad.net

Commit message

Model and basic admin-only UI for branch sharing policies.

Description of the change

This branch adds functional branch sharing policies, and basic admin-only UI to control them, using the column added in lp:~wgrant/launchpad/branch-type-policy-db.

Branch sharing policies replace BranchVisibilityPolicy. A project owner will be able to select one to determine the default and allowed information types for their branches, from the following options (names are terrible so feel free to rewrite them, but at least it works for now):

 ‣ Public: Branches are public unless they contain sensitive security information.
 ‣ Public, can be proprietary: New branches are public, but can be made proprietary later.
 ‣ Proprietary, can be public: New branches are proprietary, but can be made public later. Only people who can see the project's proprietary information can create new branches.
 ‣ Proprietary: Branches are always proprietary. Only people who can see the project's proprietary information can create new branches.

Note that while the text says Proprietary, the implementation still says User Data. This will be changed in a later branch once Proprietary is more widely supported. Branch privacy still falls back to BranchVisibilityPolicy unless Product.branch_sharing_policy is set to a non-null value, which can only currently be achieved by ~registry using Product:+admin, and only when the disclosure.branch_sharing_policy.show_to_admin feature flag is enabled. It'll later be exposed somewhere for project owners, probably on +sharing.

You can see this in action on launchpad.dev:

 - Log in as <email address hidden>.
 - Add ~name12 to ~registry at <https://launchpad.dev/~registry/+addmember>.
 - At <https://launchpad.dev/+feature-rules>, add 'disclosure.branch_sharing_policy.show_to_admin default 0 true'.
 - Log in as <email address hidden> and head over to <https://code.launchpad.dev/landscape>.
 - Set the development focus to '~name12/landscape/feature-x'.
 - Observe the privacy portlet stating default branch privacy.
 - Change the branch sharing policy at <https://launchpad.dev/landscape/+admin>, and the User Data permissions at <https://launchpad.dev/landscape/+sharing>, and observe the change in the project privacy portlet and the available information types on <https://code.launchpad.dev/~name12/landscape/feature-x/+edit>.

I also added an enum and model/interface definition for bug_sharing_policy, but they're as-yet unused.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Hi William,

Thanks for the nice write up for this merge proposal.

I wanted to see your changes so I appreciate the detailed instructions. Unfortunately at the step "Change the branch sharing policy..." I did not see the sharing policy listed. I then logged in as an administrator, saw the extra admin fields, but again did not see the branch sharing parts.

Otherwise your changes look good.

I'm going to abstain until I can verify the minimal UI. If you see a problem with the instructions you gave, or can verify it works for you still, please let me know.

review: Abstain (code)
Revision history for this message
Brad Crittenden (bac) wrote :

William I found the problem in my environment -- your instructions were perfect.

The code looks good. Thanks.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/model/branchnamespace.py'
2--- lib/lp/code/model/branchnamespace.py 2012-07-12 04:36:41 +0000
3+++ lib/lp/code/model/branchnamespace.py 2012-07-13 08:51:30 +0000
4@@ -19,6 +19,7 @@
5 from zope.interface import implements
6 from zope.security.proxy import removeSecurityProxy
7
8+from lp.app.interfaces.services import IService
9 from lp.code.enums import (
10 BranchLifecycleStatus,
11 BranchSubscriptionDiffSize,
12@@ -45,6 +46,7 @@
13 from lp.code.interfaces.branchtarget import IBranchTarget
14 from lp.code.model.branch import Branch
15 from lp.registry.enums import (
16+ BranchSharingPolicy,
17 InformationType,
18 PRIVATE_INFORMATION_TYPES,
19 PUBLIC_INFORMATION_TYPES,
20@@ -79,6 +81,29 @@
21 MAIN_STORE,
22 )
23
24+POLICY_ALLOWED_TYPES = {
25+ BranchSharingPolicy.PUBLIC: PUBLIC_INFORMATION_TYPES,
26+ BranchSharingPolicy.PUBLIC_OR_PROPRIETARY:
27+ PUBLIC_INFORMATION_TYPES + PRIVATE_INFORMATION_TYPES,
28+ BranchSharingPolicy.PROPRIETARY_OR_PUBLIC:
29+ PUBLIC_INFORMATION_TYPES + PRIVATE_INFORMATION_TYPES,
30+ BranchSharingPolicy.PROPRIETARY: PRIVATE_INFORMATION_TYPES,
31+ }
32+
33+POLICY_DEFAULT_TYPES = {
34+ BranchSharingPolicy.PUBLIC: InformationType.PUBLIC,
35+ BranchSharingPolicy.PUBLIC_OR_PROPRIETARY: InformationType.PUBLIC,
36+ BranchSharingPolicy.PROPRIETARY_OR_PUBLIC: InformationType.USERDATA,
37+ BranchSharingPolicy.PROPRIETARY: InformationType.USERDATA,
38+ }
39+
40+POLICY_REQUIRED_GRANTS = {
41+ BranchSharingPolicy.PUBLIC: None,
42+ BranchSharingPolicy.PUBLIC_OR_PROPRIETARY: None,
43+ BranchSharingPolicy.PROPRIETARY_OR_PUBLIC: InformationType.USERDATA,
44+ BranchSharingPolicy.PROPRIETARY: InformationType.USERDATA,
45+ }
46+
47
48 class _BaseNamespace:
49 """Common code for branch namespaces."""
50@@ -340,6 +365,10 @@
51 """See `IBranchNamespace`."""
52 return IBranchTarget(self.product)
53
54+ @property
55+ def _using_branchvisibilitypolicy(self):
56+ return self.product.branch_sharing_policy is None
57+
58 def _getRelatedPolicies(self):
59 """Return the privacy policies relating to the owner."""
60 policies = self.product.getBranchVisibilityTeamPolicies()
61@@ -355,6 +384,12 @@
62
63 def getPrivacySubscriber(self):
64 """See `IBranchNamespace`."""
65+ # New branch_sharing_policy-based privacy doesn't
66+ # require a privacy subscriber, as branches are shared through
67+ # AccessPolicyGrants.
68+ if not self._using_branchvisibilitypolicy:
69+ return None
70+
71 # If there is a rule defined for the owner, then there is no privacy
72 # subscriber.
73 rule = self.product.getBranchVisibilityRuleForTeam(self.owner)
74@@ -371,6 +406,24 @@
75
76 def getAllowedInformationTypes(self):
77 """See `IBranchNamespace`."""
78+ if not self._using_branchvisibilitypolicy:
79+ # The project uses the new simplified branch_sharing_policy
80+ # rules, so check them.
81+
82+ # Some policies require that the owner have full access to
83+ # an information type. If it's required and the owner
84+ # doesn't hold it, no information types are legal.
85+ required_grant = POLICY_REQUIRED_GRANTS[
86+ self.product.branch_sharing_policy]
87+ if (required_grant is not None
88+ and not getUtility(IService, 'sharing').checkPillarAccess(
89+ self.product, required_grant, self.owner)):
90+ return []
91+
92+ return POLICY_ALLOWED_TYPES[
93+ self.product.branch_sharing_policy]
94+
95+ # The project still uses BranchVisibilityPolicy, so check that.
96 private_rules = (
97 BranchVisibilityRule.PRIVATE,
98 BranchVisibilityRule.PRIVATE_ONLY)
99@@ -403,6 +456,17 @@
100 types.extend(PRIVATE_INFORMATION_TYPES)
101 return types
102
103+ def getDefaultInformationType(self):
104+ """See `IBranchNamespace`."""
105+ if not self._using_branchvisibilitypolicy:
106+ default_type = POLICY_DEFAULT_TYPES[
107+ self.product.branch_sharing_policy]
108+ if default_type not in self.getAllowedInformationTypes():
109+ return None
110+ return default_type
111+
112+ return super(ProductNamespace, self).getDefaultInformationType()
113+
114
115 class PackageNamespace(_BaseNamespace):
116 """A namespace for source package branches.
117
118=== modified file 'lib/lp/code/model/tests/test_branchnamespace.py'
119--- lib/lp/code/model/tests/test_branchnamespace.py 2012-07-11 22:16:47 +0000
120+++ lib/lp/code/model/tests/test_branchnamespace.py 2012-07-13 08:51:30 +0000
121@@ -8,6 +8,7 @@
122 from zope.component import getUtility
123 from zope.security.proxy import removeSecurityProxy
124
125+from lp.app.interfaces.services import IService
126 from lp.app.validators import LaunchpadValidationError
127 from lp.code.enums import (
128 BranchLifecycleStatus,
129@@ -36,9 +37,11 @@
130 ProductNamespace,
131 )
132 from lp.registry.enums import (
133+ BranchSharingPolicy,
134 InformationType,
135 PRIVATE_INFORMATION_TYPES,
136 PUBLIC_INFORMATION_TYPES,
137+ SharingPermission,
138 )
139 from lp.registry.errors import (
140 NoSuchDistroSeries,
141@@ -52,7 +55,11 @@
142 )
143 from lp.registry.interfaces.product import NoSuchProduct
144 from lp.registry.model.sourcepackage import SourcePackage
145-from lp.testing import TestCaseWithFactory
146+from lp.services.features.testing import FeatureFixture
147+from lp.testing import (
148+ admin_logged_in,
149+ TestCaseWithFactory,
150+ )
151 from lp.testing.layers import DatabaseFunctionalLayer
152
153
154@@ -356,8 +363,12 @@
155 self.assertEqual(IBranchTarget(product), namespace.target)
156
157
158-class TestProductNamespacePrivacy(TestCaseWithFactory):
159- """Tests for the privacy aspects of `ProductNamespace`."""
160+class TestProductNamespacePrivacyWithBranchVisibility(TestCaseWithFactory):
161+ """Tests for the privacy aspects of `ProductNamespace`.
162+
163+ This tests the behaviour for a product using the old
164+ BranchVisibilityPolicy rules.
165+ """
166
167 layer = DatabaseFunctionalLayer
168
169@@ -436,6 +447,84 @@
170 self.assertEqual(team, namespace.getPrivacySubscriber())
171
172
173+class TestProductNamespacePrivacyWithInformationType(TestCaseWithFactory):
174+ """Tests for the privacy aspects of `ProductNamespace`.
175+
176+ This tests the behaviour for a product using the new
177+ branch_sharing_policy rules.
178+ """
179+
180+ layer = DatabaseFunctionalLayer
181+
182+ def setUp(self):
183+ super(TestProductNamespacePrivacyWithInformationType, self).setUp()
184+ self.useFixture(FeatureFixture(
185+ {'disclosure.enhanced_sharing.writable': 'true'}))
186+
187+ def makeProductNamespace(self, sharing_policy, person=None):
188+ if person is None:
189+ person = self.factory.makePerson()
190+ product = self.factory.makeProduct()
191+ removeSecurityProxy(product).branch_sharing_policy = (
192+ sharing_policy)
193+ namespace = ProductNamespace(person, product)
194+ return namespace
195+
196+ def test_public_anyone(self):
197+ namespace = self.makeProductNamespace(
198+ BranchSharingPolicy.PUBLIC)
199+ self.assertContentEqual(
200+ PUBLIC_INFORMATION_TYPES, namespace.getAllowedInformationTypes())
201+ self.assertEqual(
202+ InformationType.PUBLIC, namespace.getDefaultInformationType())
203+
204+ def test_public_or_proprietary_anyone(self):
205+ namespace = self.makeProductNamespace(
206+ BranchSharingPolicy.PUBLIC_OR_PROPRIETARY)
207+ self.assertContentEqual(
208+ PUBLIC_INFORMATION_TYPES + PRIVATE_INFORMATION_TYPES,
209+ namespace.getAllowedInformationTypes())
210+ self.assertEqual(
211+ InformationType.PUBLIC, namespace.getDefaultInformationType())
212+
213+ def test_proprietary_or_public_anyone(self):
214+ namespace = self.makeProductNamespace(
215+ BranchSharingPolicy.PROPRIETARY_OR_PUBLIC)
216+ self.assertContentEqual([], namespace.getAllowedInformationTypes())
217+ self.assertIs(None, namespace.getDefaultInformationType())
218+
219+ def test_proprietary_or_public_grantor(self):
220+ namespace = self.makeProductNamespace(
221+ BranchSharingPolicy.PROPRIETARY_OR_PUBLIC)
222+ with admin_logged_in():
223+ getUtility(IService, 'sharing').sharePillarInformation(
224+ namespace.product, namespace.owner, namespace.product.owner,
225+ {InformationType.USERDATA: SharingPermission.ALL})
226+ self.assertContentEqual(
227+ PUBLIC_INFORMATION_TYPES + PRIVATE_INFORMATION_TYPES,
228+ namespace.getAllowedInformationTypes())
229+ self.assertEqual(
230+ InformationType.USERDATA, namespace.getDefaultInformationType())
231+
232+ def test_proprietary_anyone(self):
233+ namespace = self.makeProductNamespace(
234+ BranchSharingPolicy.PROPRIETARY)
235+ self.assertContentEqual([], namespace.getAllowedInformationTypes())
236+ self.assertIs(None, namespace.getDefaultInformationType())
237+
238+ def test_proprietary_grantor(self):
239+ namespace = self.makeProductNamespace(
240+ BranchSharingPolicy.PROPRIETARY)
241+ with admin_logged_in():
242+ getUtility(IService, 'sharing').sharePillarInformation(
243+ namespace.product, namespace.owner, namespace.product.owner,
244+ {InformationType.USERDATA: SharingPermission.ALL})
245+ self.assertContentEqual(
246+ PRIVATE_INFORMATION_TYPES, namespace.getAllowedInformationTypes())
247+ self.assertEqual(
248+ InformationType.USERDATA, namespace.getDefaultInformationType())
249+
250+
251 class TestPackageNamespace(TestCaseWithFactory, NamespaceMixin):
252 """Tests for `PackageNamespace`."""
253
254
255=== modified file 'lib/lp/registry/browser/product.py'
256--- lib/lp/registry/browser/product.py 2012-07-06 19:00:25 +0000
257+++ lib/lp/registry/browser/product.py 2012-07-13 08:51:30 +0000
258@@ -114,6 +114,7 @@
259 from lp.app.widgets.itemswidgets import (
260 CheckBoxMatrixWidget,
261 LaunchpadRadioWidget,
262+ LaunchpadRadioWidgetWithDescription,
263 )
264 from lp.app.widgets.popup import PersonPickerWidget
265 from lp.app.widgets.product import (
266@@ -1573,6 +1574,9 @@
267 "private_bugs",
268 ]
269
270+ custom_widget(
271+ 'branch_sharing_policy', LaunchpadRadioWidgetWithDescription)
272+
273 @property
274 def page_title(self):
275 """The HTML page title."""
276@@ -1590,6 +1594,8 @@
277 if not admin:
278 self.field_names.remove('owner')
279 self.field_names.remove('autoupdate')
280+ if getFeatureFlag('disclosure.branch_sharing_policy.show_to_admin'):
281+ self.field_names.append('branch_sharing_policy')
282 super(ProductAdminView, self).setUpFields()
283 self.form_fields = self._createAliasesField() + self.form_fields
284 if admin:
285
286=== modified file 'lib/lp/registry/configure.zcml'
287--- lib/lp/registry/configure.zcml 2012-06-29 02:15:05 +0000
288+++ lib/lp/registry/configure.zcml 2012-07-13 08:51:30 +0000
289@@ -1324,7 +1324,7 @@
290 project_reviewed
291 license_approved"
292 set_schema="lp.registry.interfaces.product.IProductModerateRestricted"
293- set_attributes="active"/>
294+ set_attributes="active branch_sharing_policy"/>
295
296 <!-- IHasAliases -->
297
298
299=== modified file 'lib/lp/registry/enums.py'
300--- lib/lp/registry/enums.py 2012-05-16 02:09:30 +0000
301+++ lib/lp/registry/enums.py 2012-07-13 08:51:30 +0000
302@@ -5,6 +5,8 @@
303
304 __metaclass__ = type
305 __all__ = [
306+ 'BranchSharingPolicy',
307+ 'BugSharingPolicy',
308 'DistroSeriesDifferenceStatus',
309 'DistroSeriesDifferenceType',
310 'InformationType',
311@@ -102,6 +104,65 @@
312 """)
313
314
315+class BranchSharingPolicy(DBEnumeratedType):
316+
317+ PUBLIC = DBItem(1, """
318+ Public
319+
320+ Branches are public unless they contain sensitive security
321+ information.
322+ """)
323+
324+ PUBLIC_OR_PROPRIETARY = DBItem(2, """
325+ Public, can be proprietary
326+
327+ New branches are public, but can be made proprietary later.
328+ """)
329+
330+ PROPRIETARY_OR_PUBLIC = DBItem(3, """
331+ Proprietary, can be public
332+
333+ New branches are proprietary, but can be made public later. Only
334+ people who can see the project's proprietary information can create
335+ new branches.
336+ """)
337+
338+ PROPRIETARY = DBItem(4, """
339+ Proprietary
340+
341+ Branches are always proprietary. Only people who can see the
342+ project's proprietary information can create new branches.
343+ """)
344+
345+
346+class BugSharingPolicy(DBEnumeratedType):
347+
348+ PUBLIC = DBItem(1, """
349+ Public
350+
351+ Bugs are public unless they contain sensitive security
352+ information.
353+ """)
354+
355+ PUBLIC_OR_PROPRIETARY = DBItem(2, """
356+ Public, can be proprietary
357+
358+ New bugs are public, but can be made proprietary later.
359+ """)
360+
361+ PROPRIETARY_OR_PUBLIC = DBItem(3, """
362+ Proprietary, can be public
363+
364+ New bugs are proprietary, but can be made public later.
365+ """)
366+
367+ PROPRIETARY = DBItem(4, """
368+ Proprietary
369+
370+ Bugs are always proprietary.
371+ """)
372+
373+
374 class DistroSeriesDifferenceStatus(DBEnumeratedType):
375 """Distribution series difference status.
376
377
378=== modified file 'lib/lp/registry/interfaces/product.py'
379--- lib/lp/registry/interfaces/product.py 2012-05-25 21:18:48 +0000
380+++ lib/lp/registry/interfaces/product.py 2012-07-13 08:51:30 +0000
381@@ -108,6 +108,10 @@
382 IHasMergeProposals,
383 )
384 from lp.code.interfaces.hasrecipes import IHasRecipes
385+from lp.registry.enums import (
386+ BranchSharingPolicy,
387+ BugSharingPolicy,
388+ )
389 from lp.registry.interfaces.announcement import IMakesAnnouncements
390 from lp.registry.interfaces.commercialsubscription import (
391 ICommercialSubscription,
392@@ -634,6 +638,17 @@
393 description=_(
394 "Whether or not bugs reported into this project "
395 "are private by default.")))
396+ branch_sharing_policy = exported(Choice(
397+ title=_('Branch sharing policy'),
398+ description=_("Sharing policy for this project's branches."),
399+ required=False, readonly=False, vocabulary=BranchSharingPolicy),
400+ as_of='devel')
401+ bug_sharing_policy = exported(Choice(
402+ title=_('Bug sharing policy'),
403+ description=_("Sharing policy for this project's bugs."),
404+ required=False, readonly=True, vocabulary=BugSharingPolicy),
405+ as_of='devel')
406+
407 licenses = exported(
408 Set(title=_('Licences'),
409 value_type=Choice(vocabulary=License)))
410
411=== modified file 'lib/lp/registry/interfaces/sharingservice.py'
412--- lib/lp/registry/interfaces/sharingservice.py 2012-06-18 03:23:18 +0000
413+++ lib/lp/registry/interfaces/sharingservice.py 2012-07-13 08:51:30 +0000
414@@ -45,6 +45,13 @@
415 # version 'devel'
416 export_as_webservice_entry(publish_web_link=False, as_of='beta')
417
418+ def checkPillarAccess(pillar, information_type, person):
419+ """Check the person's access to the given pillar and information type.
420+
421+ :return: True if the user has access to all the pillar's information
422+ of that type, False otherwise
423+ """
424+
425 def getSharedArtifacts(pillar, person, user):
426 """Return the artifacts shared between the pillar and person.
427
428
429=== modified file 'lib/lp/registry/model/product.py'
430--- lib/lp/registry/model/product.py 2012-07-09 04:14:09 +0000
431+++ lib/lp/registry/model/product.py 2012-07-13 08:51:30 +0000
432@@ -116,7 +116,11 @@
433 )
434 from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
435 from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
436-from lp.registry.enums import InformationType
437+from lp.registry.enums import (
438+ BranchSharingPolicy,
439+ BugSharingPolicy,
440+ InformationType,
441+ )
442 from lp.registry.errors import CommercialSubscribersOnly
443 from lp.registry.interfaces.accesspolicy import (
444 IAccessPolicySource,
445@@ -469,6 +473,10 @@
446 reviewer_whiteboard = StringCol(notNull=False, default=None)
447 private_bugs = BoolCol(
448 dbName='private_bugs', notNull=True, default=False)
449+ bug_sharing_policy = EnumCol(
450+ enum=BugSharingPolicy, notNull=False, default=None)
451+ branch_sharing_policy = EnumCol(
452+ enum=BranchSharingPolicy, notNull=False, default=None)
453 autoupdate = BoolCol(dbName='autoupdate', notNull=True, default=False)
454 freshmeatproject = StringCol(notNull=False, default=None)
455 sourceforgeproject = StringCol(notNull=False, default=None)
456
457=== modified file 'lib/lp/registry/services/sharingservice.py'
458--- lib/lp/registry/services/sharingservice.py 2012-07-02 23:40:56 +0000
459+++ lib/lp/registry/services/sharingservice.py 2012-07-13 08:51:30 +0000
460@@ -84,6 +84,25 @@
461 bool(getFeatureFlag(
462 'disclosure.enhanced_sharing.writable')))
463
464+ def checkPillarAccess(self, pillar, information_type, person):
465+ """See `ISharingService`."""
466+ policy = getUtility(IAccessPolicySource).find(
467+ [(pillar, information_type)]).one()
468+ if policy is None:
469+ return False
470+ store = IStore(AccessPolicyGrant)
471+ tables = [
472+ AccessPolicyGrant,
473+ Join(
474+ TeamParticipation,
475+ TeamParticipation.teamID == AccessPolicyGrant.grantee_id),
476+ ]
477+ result = store.using(*tables).find(
478+ AccessPolicyGrant,
479+ AccessPolicyGrant.policy_id == policy.id,
480+ TeamParticipation.personID == person.id)
481+ return not result.is_empty()
482+
483 def getSharedArtifacts(self, pillar, person, user):
484 """See `ISharingService`."""
485 policies = getUtility(IAccessPolicySource).findByPillar([pillar])
486
487=== modified file 'lib/lp/registry/services/tests/test_sharingservice.py'
488--- lib/lp/registry/services/tests/test_sharingservice.py 2012-07-11 01:21:56 +0000
489+++ lib/lp/registry/services/tests/test_sharingservice.py 2012-07-13 08:51:30 +0000
490@@ -39,6 +39,7 @@
491 from lp.services.webapp.interfaces import ILaunchpadRoot
492 from lp.services.webapp.publisher import canonical_url
493 from lp.testing import (
494+ admin_logged_in,
495 login,
496 login_person,
497 StormStatementRecorder,
498@@ -493,7 +494,7 @@
499 [InformationType.USERDATA,
500 InformationType.EMBARGOEDSECURITY]),
501 ]
502- else:
503+ else:
504 expected_sharee_grants = [
505 (sharee,
506 {es_policy: SharingPermission.ALL,
507@@ -1253,6 +1254,38 @@
508
509 self._assert_getVisibleArtifacts_bug_change(retarget_bugtask)
510
511+ def test_checkPillarAccess(self):
512+ # checkPillarAccess checks whether the user has full access to
513+ # an information type.
514+ product = self.factory.makeProduct()
515+ right_person = self.factory.makePerson()
516+ right_team = self.factory.makeTeam(members=[right_person])
517+ wrong_person = self.factory.makePerson()
518+ with FeatureFixture(WRITE_FLAG):
519+ with admin_logged_in():
520+ self.service.sharePillarInformation(
521+ product, right_team, product.owner,
522+ {InformationType.USERDATA: SharingPermission.ALL})
523+ self.service.sharePillarInformation(
524+ product, wrong_person, product.owner,
525+ {InformationType.EMBARGOEDSECURITY: SharingPermission.ALL})
526+ self.assertEqual(
527+ False,
528+ self.service.checkPillarAccess(
529+ product, InformationType.USERDATA, wrong_person))
530+ self.assertEqual(
531+ True,
532+ self.service.checkPillarAccess(
533+ product, InformationType.USERDATA, right_person))
534+
535+ def test_checkPillarAccess_no_policy(self):
536+ # checkPillarAccess returns False if there's no policy.
537+ self.assertEqual(
538+ False,
539+ self.service.checkPillarAccess(
540+ self.factory.makeProduct(), InformationType.PUBLIC,
541+ self.factory.makePerson()))
542+
543
544 class ApiTestMixin:
545 """Common tests for launchpadlib and webservice."""
546
547=== modified file 'lib/lp/services/features/flags.py'
548--- lib/lp/services/features/flags.py 2012-07-11 01:24:42 +0000
549+++ lib/lp/services/features/flags.py 2012-07-13 08:51:30 +0000
550@@ -318,6 +318,13 @@
551 '',
552 '',
553 ''),
554+ ('disclosure.branch_sharing_policy.show_to_admin',
555+ 'boolean',
556+ ('If true, the branch sharing policy field is shown on '
557+ 'Product:+admin, letting BranchVisibilityPolicy be overridden.'),
558+ '',
559+ '',
560+ ''),
561 ])
562
563 # The set of all flag names that are documented.