Merge lp:~wallyworld/launchpad/subscribe-grants-access-1000045 into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 15359
Proposed branch: lp:~wallyworld/launchpad/subscribe-grants-access-1000045
Merge into: lp:launchpad
Prerequisite: lp:~wallyworld/launchpad/legacy-unsubscribe-revokes-access-997425
Diff against target: 463 lines (+273/-21)
9 files modified
configs/testrunner/launchpad-lazr.conf (+0/-15)
lib/lp/bugs/model/bug.py (+10/-0)
lib/lp/bugs/tests/test_bugvisibility.py (+22/-0)
lib/lp/registry/interfaces/accesspolicy.py (+1/-1)
lib/lp/registry/interfaces/sharingservice.py (+30/-0)
lib/lp/registry/services/sharingservice.py (+62/-4)
lib/lp/registry/services/tests/test_sharingservice.py (+135/-1)
lib/lp/services/features/flags.py (+7/-0)
lib/lp/testing/factory.py (+6/-0)
To merge this branch: bzr merge lp:~wallyworld/launchpad/subscribe-grants-access-1000045
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+106278@code.launchpad.net

Commit message

Add model code to grant access to bugs when adding a subscriber and add required sharing service methods

Description of the change

== Implementation ==

This branch provides code to grant access to a bug when a user is subscribed (if the bug is not already visible to the user). This is done right now by triggers, but the triggers will be removed at some point.

Key implementation points:

Add feature flag disclosure.access_mirror_triggers.removed which needs to be turned on for everybody during the FDT when the triggers are deleted.

Add 2 new methods to the sharing service:
- getVisibleArtifacts(person, branches=None, bugs=None)
- createAccessGrants(user, sharee, branches=None, bugs=None)

The bug subscribe code checks that triggers are removed and if so, checks whether the bug is visible to the subscriber, and if not, grants access.

The permission check in the createAccessGrants() method checks for launchpad.Edit on each bug/branch. This matches the permission required to subscribe the user in the first place. However, other writeable operations on the sharing service required launchpad.Edit on the pillar. The check on the createAccessGrants() method could be tightened, but then it may not allow a user to subscribe someone since it could raise Unauthorised when a user has launchpad.Edit on the bug but not launchpad.Edit on a target pillar.

== Tests ==

Add tests for the new sharing service methods.
- test_createAccessGrantsXXXX
- test_getVisibleArtifacts

Add tests for the bug subscription behaviour:
- test_subscribeGrantsVisibilityWithTriggersRemoved
- test_subscribeGrantsVisibilityUsingTriggers

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/model/bug.py
  lib/lp/bugs/tests/test_bugvisibility.py
  lib/lp/registry/interfaces/accesspolicy.py
  lib/lp/registry/interfaces/sharingservice.py
  lib/lp/registry/services/sharingservice.py
  lib/lp/registry/services/tests/test_sharingservice.py
  lib/lp/services/features/flags.py
  lib/lp/testing/factory.py

./lib/lp/testing/factory.py
    1129: E501 line too long (80 characters)

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

I think this is good to land. We need a bug number for the two XXX, which might be bug 933768 or bug 933938.

I think we will need to revisit the permission rules in a few weeks. The PM team delegates the running of the project to the drivers, who work with the contractors. Drivers do not get to set project-level sharing, but they probably need to manage bugs and branch sharing. We need Matthew to get this answer.

review: Approve (code)
Revision history for this message
Ian Booth (wallyworld) wrote :

On 18/05/12 08:51, Curtis Hovey wrote:
> Review: Approve code
>
> I think this is good to land. We need a bug number for the two XXX, which might be bug 933768 or bug 933938.

I created a new bug 1001042 "Update BranchCollection filtering to use
branch information_type" since it is pretty much a self contained body
of work.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'configs/testrunner/launchpad-lazr.conf'
--- configs/testrunner/launchpad-lazr.conf 2012-05-21 00:05:24 +0000
+++ configs/testrunner/launchpad-lazr.conf 2012-05-15 07:22:45 +0000
@@ -150,21 +150,6 @@
150# processes spawned through some other mechanism.150# processes spawned through some other mechanism.
151port: 11242151port: 11242
152152
153<<<<<<< TREE
154=======
155[merge_proposal_jobs]
156error_dir: /var/tmp/codehosting.test
157
158[packaging_translations]
159error_dir: /var/tmp/lperr.test
160
161[sharing_jobs]
162error_dir: /var/tmp/sharing.test
163
164[upgrade_branches]
165error_dir: /var/tmp/codehosting.test
166
167>>>>>>> MERGE-SOURCE
168[personalpackagearchive]153[personalpackagearchive]
169root: /var/tmp/ppa.test/154root: /var/tmp/ppa.test/
170155
171156
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2012-05-21 00:05:24 +0000
+++ lib/lp/bugs/model/bug.py 2012-05-21 00:05:24 +0000
@@ -4,6 +4,7 @@
4# pylint: disable-msg=E0611,W02124# pylint: disable-msg=E0611,W0212
55
6"""Launchpad bug-related database table classes."""6"""Launchpad bug-related database table classes."""
7from lp.app.interfaces.services import IService
78
8__metaclass__ = type9__metaclass__ = type
910
@@ -839,6 +840,15 @@
839 # Ensure that the subscription has been flushed.840 # Ensure that the subscription has been flushed.
840 Store.of(sub).flush()841 Store.of(sub).flush()
841842
843 # Grant the subscriber access if they can't see the bug (if the
844 # database triggers aren't going to do it for us).
845 trigger_flag = 'disclosure.access_mirror_triggers.removed'
846 if bool(getFeatureFlag(trigger_flag)):
847 service = getUtility(IService, 'sharing')
848 bugs, ignored = service.getVisibleArtifacts(person, bugs=[self])
849 if not bugs:
850 service.createAccessGrants(subscribed_by, person, bugs=[self])
851
842 # In some cases, a subscription should be created without852 # In some cases, a subscription should be created without
843 # email notifications. suppress_notify determines if853 # email notifications. suppress_notify determines if
844 # notifications are sent.854 # notifications are sent.
845855
=== modified file 'lib/lp/bugs/tests/test_bugvisibility.py'
--- lib/lp/bugs/tests/test_bugvisibility.py 2012-05-21 00:05:24 +0000
+++ lib/lp/bugs/tests/test_bugvisibility.py 2012-05-21 00:05:24 +0000
@@ -18,6 +18,8 @@
1818
19LEGACY_VISIBILITY_FLAG = {19LEGACY_VISIBILITY_FLAG = {
20 u"disclosure.legacy_subscription_visibility.enabled": u"true"}20 u"disclosure.legacy_subscription_visibility.enabled": u"true"}
21TRIGGERS_REMOVED_FLAG = {
22 u"disclosure.access_mirror_triggers.removed": u"true"}
2123
2224
23class TestPublicBugVisibility(TestCaseWithFactory):25class TestPublicBugVisibility(TestCaseWithFactory):
@@ -123,3 +125,23 @@
123 with self.disable_trigger_fixture:125 with self.disable_trigger_fixture:
124 self.bug.unsubscribe(user, self.owner)126 self.bug.unsubscribe(user, self.owner)
125 self.assertTrue(self.bug.userCanView(user))127 self.assertTrue(self.bug.userCanView(user))
128
129 def test_subscribeGrantsVisibilityWithTriggersRemoved(self):
130 # When a user is subscribed to a bug, they are granted access. In this
131 # test, the database triggers are removed and so model code is used.
132 with FeatureFixture(TRIGGERS_REMOVED_FLAG):
133 with self.disable_trigger_fixture:
134 user = self.factory.makePerson()
135 self.assertFalse(self.bug.userCanView(user))
136 with celebrity_logged_in('admin'):
137 self.bug.subscribe(user, self.owner)
138 self.assertTrue(self.bug.userCanView(user))
139
140 def test_subscribeGrantsVisibilityUsingTriggers(self):
141 # When a user is subscribed to a bug, they are granted access. In this
142 # test, the database triggers are used.
143 user = self.factory.makePerson()
144 self.assertFalse(self.bug.userCanView(user))
145 with celebrity_logged_in('admin'):
146 self.bug.subscribe(user, self.owner)
147 self.assertTrue(self.bug.userCanView(user))
126148
=== modified file 'lib/lp/registry/interfaces/accesspolicy.py'
--- lib/lp/registry/interfaces/accesspolicy.py 2012-05-21 00:05:24 +0000
+++ lib/lp/registry/interfaces/accesspolicy.py 2012-05-21 00:05:24 +0000
@@ -283,5 +283,5 @@
283 """Find the `IAccessArtifact`s for grantee and policies.283 """Find the `IAccessArtifact`s for grantee and policies.
284284
285 :param grantee: the access artifact grantee.285 :param grantee: the access artifact grantee.
286 :param policies: a collection of `IAccesPolicy`s.286 :param policies: a collection of `IAccessPolicy`s.
287 """287 """
288288
=== modified file 'lib/lp/registry/interfaces/sharingservice.py'
--- lib/lp/registry/interfaces/sharingservice.py 2012-05-21 00:05:24 +0000
+++ lib/lp/registry/interfaces/sharingservice.py 2012-05-21 00:05:24 +0000
@@ -58,6 +58,18 @@
58 :return: a (bugtasks, branches) tuple58 :return: a (bugtasks, branches) tuple
59 """59 """
6060
61 def getVisibleArtifacts(person, branches=None, bugs=None):
62 """Return the artifacts shared with person.
63
64 Given lists of artifacts, return those a person has access to either
65 via a policy grant or artifact grant.
66
67 :param person: the person whose access is being checked.
68 :param branches: the branches to check for which a person has access.
69 :param bugs: the bugs to check for which a person has access.
70 :return: a collection of artifacts the person can see.
71 """
72
61 def getInformationTypes(pillar):73 def getInformationTypes(pillar):
62 """Return the allowed information types for the given pillar."""74 """Return the allowed information types for the given pillar."""
6375
@@ -149,3 +161,21 @@
149 :param bugs: the bugs for which to revoke access161 :param bugs: the bugs for which to revoke access
150 :param branches: the branches for which to revoke access162 :param branches: the branches for which to revoke access
151 """163 """
164
165 @export_write_operation()
166 @call_with(user=REQUEST_USER)
167 @operation_parameters(
168 sharee=Reference(IPerson, title=_('Sharee'), required=True),
169 bugs=List(
170 Reference(schema=IBug), title=_('Bugs'), required=False),
171 branches=List(
172 Reference(schema=IBranch), title=_('Branches'), required=False))
173 @operation_for_version('devel')
174 def createAccessGrants(user, sharee, branches=None, bugs=None):
175 """Grant a sharee access to the specified artifacts.
176
177 :param user: the user making the request
178 :param sharee: the person or team for whom to grant access
179 :param bugs: the bugs for which to grant access
180 :param branches: the branches for which to grant access
181 """
152182
=== modified file 'lib/lp/registry/services/sharingservice.py'
--- lib/lp/registry/services/sharingservice.py 2012-05-21 00:05:24 +0000
+++ lib/lp/registry/services/sharingservice.py 2012-05-21 00:05:24 +0000
@@ -8,6 +8,8 @@
8 'SharingService',8 'SharingService',
9 ]9 ]
1010
11from itertools import product
12
11from lazr.restful.interfaces import IWebBrowserOriginatingRequest13from lazr.restful.interfaces import IWebBrowserOriginatingRequest
12from lazr.restful.utils import get_current_web_service_request14from lazr.restful.utils import get_current_web_service_request
13from zope.component import getUtility15from zope.component import getUtility
@@ -31,7 +33,6 @@
31 IAccessPolicyGrantSource,33 IAccessPolicyGrantSource,
32 IAccessPolicySource,34 IAccessPolicySource,
33 )35 )
34from lp.registry.interfaces.distribution import IDistribution
35from lp.registry.interfaces.product import IProduct36from lp.registry.interfaces.product import IProduct
36from lp.registry.interfaces.projectgroup import IProjectGroup37from lp.registry.interfaces.projectgroup import IProjectGroup
37from lp.registry.interfaces.sharingjob import IRemoveSubscriptionsJobSource38from lp.registry.interfaces.sharingjob import IRemoveSubscriptionsJobSource
@@ -39,7 +40,10 @@
39from lp.registry.model.person import Person40from lp.registry.model.person import Person
40from lp.services.features import getFeatureFlag41from lp.services.features import getFeatureFlag
41from lp.services.searchbuilder import any42from lp.services.searchbuilder import any
42from lp.services.webapp.authorization import available_with_permission43from lp.services.webapp.authorization import (
44 available_with_permission,
45 check_permission,
46 )
4347
4448
45class SharingService:49class SharingService:
@@ -58,8 +62,11 @@
5862
59 @property63 @property
60 def write_enabled(self):64 def write_enabled(self):
61 return bool(getFeatureFlag(65 return (
62 'disclosure.enhanced_sharing.writable'))66 bool(getFeatureFlag(
67 'disclosure.enhanced_sharing.writable') or
68 bool(getFeatureFlag(
69 'disclosure.access_mirror_triggers.removed'))))
6370
64 def getSharedArtifacts(self, pillar, person, user):71 def getSharedArtifacts(self, pillar, person, user):
65 """See `ISharingService`."""72 """See `ISharingService`."""
@@ -89,6 +96,33 @@
8996
90 return bugtasks, branches97 return bugtasks, branches
9198
99 def getVisibleArtifacts(self, person, branches=None, bugs=None):
100 """See `ISharingService`."""
101 bugs_by_id = {}
102 branches_by_id = {}
103 for bug in bugs or []:
104 bugs_by_id[bug.id] = bug
105 for branch in branches or []:
106 branches_by_id[branch.id] = branch
107
108 # Load the bugs.
109 visible_bug_ids = []
110 if bugs_by_id:
111 param = BugTaskSearchParams(
112 user=person, bug=any(*bugs_by_id.keys()))
113 visible_bug_ids = list(getUtility(IBugTaskSet).searchBugIds(param))
114 visible_bugs = [bugs_by_id[bug_id] for bug_id in visible_bug_ids]
115
116 # Load the branches.
117 visible_branches = []
118 if branches_by_id:
119 all_branches = getUtility(IAllBranches)
120 wanted_branches = all_branches.visibleByUser(person).withIds(
121 *branches_by_id.keys())
122 visible_branches = list(wanted_branches.getBranches())
123
124 return visible_bugs, visible_branches
125
92 def getInformationTypes(self, pillar):126 def getInformationTypes(self, pillar):
93 """See `ISharingService`."""127 """See `ISharingService`."""
94 allowed_types = [128 allowed_types = [
@@ -313,3 +347,27 @@
313 # longer see.347 # longer see.
314 getUtility(IRemoveSubscriptionsJobSource).create(348 getUtility(IRemoveSubscriptionsJobSource).create(
315 pillar, sharee, user, bugs=bugs, branches=branches)349 pillar, sharee, user, bugs=bugs, branches=branches)
350
351 def createAccessGrants(self, user, sharee, branches=None, bugs=None):
352 """See `ISharingService`."""
353
354 if not self.write_enabled:
355 raise Unauthorized("This feature is not yet enabled.")
356
357 artifacts = []
358 if branches:
359 artifacts.extend(branches)
360 if bugs:
361 artifacts.extend(bugs)
362 # The user needs to have launchpad.Edit permission on all supplied
363 # bugs and branches or else we raise an Unauthorized exception.
364 for artifact in artifacts or []:
365 if not check_permission('launchpad.Edit', artifact):
366 raise Unauthorized
367
368 # Ensure there are access artifacts associated with the bugs and
369 # branches.
370 artifacts = getUtility(IAccessArtifactSource).ensure(artifacts)
371 # Create access to bugs/branches for the specified sharee.
372 getUtility(IAccessArtifactGrantSource).grant(
373 list(product(artifacts, [sharee], [user])))
316374
=== modified file 'lib/lp/registry/services/tests/test_sharingservice.py'
--- lib/lp/registry/services/tests/test_sharingservice.py 2012-05-21 00:05:24 +0000
+++ lib/lp/registry/services/tests/test_sharingservice.py 2012-05-21 00:05:24 +0000
@@ -13,11 +13,13 @@
13from zope.traversing.browser.absoluteurl import absoluteURL13from zope.traversing.browser.absoluteurl import absoluteURL
1414
15from lp.app.interfaces.services import IService15from lp.app.interfaces.services import IService
16from lp.bugs.interfaces.bug import IBug
16from lp.code.enums import (17from lp.code.enums import (
17 BranchSubscriptionDiffSize,18 BranchSubscriptionDiffSize,
18 BranchSubscriptionNotificationLevel,19 BranchSubscriptionNotificationLevel,
19 CodeReviewNotificationLevel,20 CodeReviewNotificationLevel,
20 )21 )
22from lp.code.interfaces.branch import IBranch
21from lp.registry.enums import (23from lp.registry.enums import (
22 InformationType,24 InformationType,
23 SharingPermission,25 SharingPermission,
@@ -786,6 +788,89 @@
786 Unauthorized, self.service.revokeAccessGrants,788 Unauthorized, self.service.revokeAccessGrants,
787 product, product.owner, sharee, bugs=[bug])789 product, product.owner, sharee, bugs=[bug])
788790
791 def _assert_createAccessGrants(self, user, bugs, branches):
792 # Creating access grants works as expected.
793 grantee = self.factory.makePerson()
794 with FeatureFixture(WRITE_FLAG):
795 self.service.createAccessGrants(
796 user, grantee, bugs=bugs, branches=branches)
797
798 # Check that grantee has expected access grants.
799 shared_bugs = []
800 shared_branches = []
801 all_pillars = []
802 for bug in bugs or []:
803 all_pillars.extend(bug.affected_pillars)
804 for branch in branches or []:
805 all_pillars.append(branch.target.context)
806 policies = getUtility(IAccessPolicySource).findByPillar(all_pillars)
807
808 apgfs = getUtility(IAccessPolicyGrantFlatSource)
809 access_artifacts = apgfs.findArtifactsByGrantee(grantee, policies)
810 for a in access_artifacts:
811 if IBug.providedBy(a.concrete_artifact):
812 shared_bugs.append(a.concrete_artifact)
813 elif IBranch.providedBy(a.concrete_artifact):
814 shared_branches.append(a.concrete_artifact)
815 self.assertContentEqual(bugs or [], shared_bugs)
816 self.assertContentEqual(branches or [], shared_branches)
817
818 def test_createAccessGrantsBugs(self):
819 # Access grants can be created for bugs.
820 owner = self.factory.makePerson()
821 distro = self.factory.makeDistribution(owner=owner)
822 login_person(owner)
823 bug = self.factory.makeBug(
824 distribution=distro, owner=owner,
825 information_type=InformationType.USERDATA)
826 self._assert_createAccessGrants(owner, [bug], None)
827
828 def test_createAccessGrantsBranches(self):
829 # Access grants can be created for branches.
830 owner = self.factory.makePerson()
831 product = self.factory.makeProduct(owner=owner)
832 login_person(owner)
833 branch = self.factory.makeBranch(
834 product=product, owner=owner, private=True)
835 self._assert_createAccessGrants(owner, None, [branch])
836
837 def _assert_createAccessGrantsUnauthorized(self, user):
838 # createAccessGrants raises an Unauthorized exception if the user
839 # is not permitted to do so.
840 product = self.factory.makeProduct()
841 bug = self.factory.makeBug(
842 product=product, information_type=InformationType.USERDATA)
843 sharee = self.factory.makePerson()
844 with FeatureFixture(WRITE_FLAG):
845 self.assertRaises(
846 Unauthorized, self.service.createAccessGrants,
847 user, sharee, bugs=[bug])
848
849 def test_createAccessGrantsAnonymous(self):
850 # Anonymous users are not allowed.
851 with FeatureFixture(WRITE_FLAG):
852 login(ANONYMOUS)
853 self._assert_createAccessGrantsUnauthorized(ANONYMOUS)
854
855 def test_createAccessGrantsAnyone(self):
856 # Unauthorized users are not allowed.
857 with FeatureFixture(WRITE_FLAG):
858 anyone = self.factory.makePerson()
859 login_person(anyone)
860 self._assert_createAccessGrantsUnauthorized(anyone)
861
862 def test_createAccessGrants_without_flag(self):
863 # The feature flag needs to be enabled.
864 owner = self.factory.makePerson()
865 product = self.factory.makeProduct(owner=owner)
866 bug = self.factory.makeBug(
867 product=product, information_type=InformationType.USERDATA)
868 sharee = self.factory.makePerson()
869 login_person(owner)
870 self.assertRaises(
871 Unauthorized, self.service.createAccessGrants,
872 product.owner, sharee, bugs=[bug])
873
789 def test_getSharedArtifacts(self):874 def test_getSharedArtifacts(self):
790 # Test the getSharedArtifacts method.875 # Test the getSharedArtifacts method.
791 owner = self.factory.makePerson()876 owner = self.factory.makePerson()
@@ -829,7 +914,8 @@
829 [(product, InformationType.USERDATA)])914 [(product, InformationType.USERDATA)])
830 for i, branch in enumerate(branches):915 for i, branch in enumerate(branches):
831 artifact = grant_access(branch, i == 9)916 artifact = grant_access(branch, i == 9)
832 # XXX for now we need to subscribe users to the branch in order917 # XXX bug=1001042 wallyworld 2012-05-18
918 # for now we need to subscribe users to the branch in order
833 # for the underlying BranchCollection to allow access. This will919 # for the underlying BranchCollection to allow access. This will
834 # no longer be the case when BranchCollection supports the new920 # no longer be the case when BranchCollection supports the new
835 # access policy framework.921 # access policy framework.
@@ -848,6 +934,54 @@
848 self.assertContentEqual(bug_tasks[:9], shared_bugtasks)934 self.assertContentEqual(bug_tasks[:9], shared_bugtasks)
849 self.assertContentEqual(branches[:9], shared_branches)935 self.assertContentEqual(branches[:9], shared_branches)
850936
937 def test_getVisibleArtifacts(self):
938 # Test the getVisibleArtifacts method.
939 owner = self.factory.makePerson()
940 product = self.factory.makeProduct(owner=owner)
941 grantee = self.factory.makePerson()
942 login_person(owner)
943
944 bugs = []
945 for x in range(0, 10):
946 bug = self.factory.makeBug(
947 product=product, owner=owner,
948 information_type=InformationType.USERDATA)
949 bugs.append(bug)
950 branches = []
951 for x in range(0, 10):
952 branch = self.factory.makeBranch(
953 product=product, owner=owner, private=True)
954 branches.append(branch)
955
956 def grant_access(artifact):
957 access_artifact = self.factory.makeAccessArtifact(
958 concrete=artifact)
959 self.factory.makeAccessArtifactGrant(
960 artifact=access_artifact, grantee=grantee, grantor=owner)
961 return access_artifact
962
963 # Grant access to some of the bugs and branches.
964 for bug in bugs[:5]:
965 grant_access(bug)
966 for branch in branches[:5]:
967 grant_access(branch)
968 # XXX bug=1001042 wallyworld 2012-05-18
969 # for now we need to subscribe users to the branch in order
970 # for the underlying BranchCollection to allow access. This will
971 # no longer be the case when BranchCollection supports the new
972 # access policy framework.
973 branch.subscribe(
974 grantee, BranchSubscriptionNotificationLevel.NOEMAIL,
975 BranchSubscriptionDiffSize.NODIFF,
976 CodeReviewNotificationLevel.NOEMAIL,
977 owner)
978
979 # Check the results.
980 shared_bugs, shared_branches = self.service.getVisibleArtifacts(
981 grantee, branches, bugs)
982 self.assertContentEqual(bugs[:5], shared_bugs)
983 self.assertContentEqual(branches[:5], shared_branches)
984
851985
852class ApiTestMixin:986class ApiTestMixin:
853 """Common tests for launchpadlib and webservice."""987 """Common tests for launchpadlib and webservice."""
854988
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2012-05-21 00:05:24 +0000
+++ lib/lp/services/features/flags.py 2012-05-21 00:05:24 +0000
@@ -295,6 +295,13 @@
295 '',295 '',
296 'Sharing management',296 'Sharing management',
297 ''),297 ''),
298 ('disclosure.access_mirror_triggers.removed',
299 'boolean',
300 ('If true, the database triggers which cause subscribing to grant'
301 'access to a bug or branch have been removed.'),
302 '',
303 '',
304 ''),
298 ('garbo.workitem_migrator.enabled',305 ('garbo.workitem_migrator.enabled',
299 'boolean',306 'boolean',
300 ('If true, garbo will try to migrate work items from the whiteboard of '307 ('If true, garbo will try to migrate work items from the whiteboard of '
301308
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2012-05-04 14:52:44 +0000
+++ lib/lp/testing/factory.py 2012-05-21 00:05:24 +0000
@@ -1122,6 +1122,12 @@
1122 if private:1122 if private:
1123 removeSecurityProxy(branch).explicitly_private = True1123 removeSecurityProxy(branch).explicitly_private = True
1124 removeSecurityProxy(branch).transitively_private = True1124 removeSecurityProxy(branch).transitively_private = True
1125 # XXX this is here till branch properly supports information_type
1126 [artifact] = getUtility(IAccessArtifactSource).ensure([branch])
1127 [policy] = getUtility(IAccessPolicySource).find(
1128 [(branch.target.context, InformationType.USERDATA)])
1129 getUtility(IAccessPolicyArtifactSource).create([(artifact, policy)])
1130
1125 if stacked_on is not None:1131 if stacked_on is not None:
1126 removeSecurityProxy(branch).stacked_on = stacked_on1132 removeSecurityProxy(branch).stacked_on = stacked_on
1127 if reviewer is not None:1133 if reviewer is not None: