Merge lp:~abentley/launchpad/transitive-confidential into lp:launchpad

Proposed by Aaron Bentley on 2012-11-19
Status: Superseded
Proposed branch: lp:~abentley/launchpad/transitive-confidential
Merge into: lp:launchpad
Diff against target: 347 lines (+97/-167)
4 files modified
lib/lp/registry/browser/product.py (+0/-41)
lib/lp/registry/browser/tests/test_product.py (+0/-113)
lib/lp/registry/model/product.py (+40/-13)
lib/lp/registry/tests/test_product.py (+57/-0)
To merge this branch: bzr merge lp:~abentley/launchpad/transitive-confidential
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Needs Fixing on 2012-11-22
Richard Harding (community) 2012-11-19 Approve on 2012-11-19
Review via email: mp+134995@code.launchpad.net

This proposal has been superseded by a proposal from 2012-11-23.

Commit Message

Forbid public artifacts on confidential products.

Description of the Change

= Summary =
Fix bug #1079785: public artifacts are permitted on confidential projects

== Proposed fix ==
Move checks from view to model and raise exceptions. Add shared bug check.

== Pre-implementation notes ==
None

== LOC Rationale ==
Reduces LOC

== Implementation details ==
Revert 16258 and migrate code from getPublicWarning. Add check for shared bugs.

Remove getPublicWarning and related code and tests.

== Tests ==
bin/test -t test_change_info_type_proprietary_check_artifacts -t test_change_info_type_check_shared

== Demo and Q/A ==
Create a public product with other/proprietary license. Create a public branch, bug, blueprint. Create a private shared bug. You should not be able to change its information type to proprietary until you've made all artifacts private and removed the extra bugtask from the shared bug.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/browser/product.py
  lib/lp/registry/browser/tests/test_product.py
  lib/lp/registry/model/product.py
  lib/lp/registry/tests/test_product.py

To post a comment you must log in.
Richard Harding (rharding) :
review: Approve
Aaron Bentley (abentley) wrote :

Further discussion with Curtis reveals that only Proprietary and Embargoed artefacts should be permitted when transitioning product to proprietary/embargoed. See bug.

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/product.py'
2--- lib/lp/registry/browser/product.py 2012-11-15 16:03:56 +0000
3+++ lib/lp/registry/browser/product.py 2012-11-23 14:31:20 +0000
4@@ -105,7 +105,6 @@
5 InformationType,
6 PROPRIETARY_INFORMATION_TYPES,
7 PUBLIC_PROPRIETARY_INFORMATION_TYPES,
8- PUBLIC_INFORMATION_TYPES,
9 ServiceUsage,
10 )
11 from lp.app.errors import NotFoundError
12@@ -129,9 +128,6 @@
13 from lp.blueprints.browser.specificationtarget import (
14 HasSpecificationsMenuMixin,
15 )
16-from lp.blueprints.enums import (
17- SpecificationFilter,
18- )
19 from lp.bugs.browser.bugtask import (
20 BugTargetTraversalMixin,
21 get_buglisting_search_filter_url,
22@@ -1450,47 +1446,10 @@
23
24 @action("Change", name='change')
25 def change_action(self, action, data):
26- old_info_type = self.context.information_type
27 try:
28 self.updateContextFromData(data)
29 except CannotChangeInformationType as e:
30 self.setFieldError('information_type', str(e))
31- warning = self.getPublicWarning(old_info_type)
32- if warning is not None:
33- self.request.response.addWarningNotification(warning)
34-
35- def getPublicWarning(self, old_info_type):
36- """Get warning if public artifacts might expose the product.
37-
38- :param old_info_type: The former information type of the product.
39- """
40- if (old_info_type != InformationType.PUBLIC or
41- self.context.information_type == InformationType.PUBLIC):
42- return None
43- public_bugs = self.context.searchTasks(None,
44- information_type=PUBLIC_INFORMATION_TYPES,
45- omit_duplicates=False, omit_targeted=False)
46- public_artifacts = []
47- if not public_bugs.is_empty():
48- public_artifacts.append('bugs')
49- # Default returns all public branches.
50- public_branches = self.context.getBranches()
51- if not public_branches.is_empty():
52- public_artifacts.append('branches')
53- # All specs located by an ALL search are public.
54- public_specs = self.context.specifications(
55- None, filter=[SpecificationFilter.ALL])
56- if not public_specs.is_empty():
57- public_artifacts.append('blueprints')
58- if len(public_artifacts) == 0:
59- return None
60- elif len(public_artifacts) < 3:
61- list_phrase = ' and '.join(public_artifacts)
62- else:
63- list_phrase = 'bugs, branches, and blueprints'
64- return ('Some %s are public. This project will not be fully '
65- '%s until they have been changed.' %
66- (list_phrase, self.context.information_type.title))
67
68
69 class ProductValidationMixin:
70
71=== modified file 'lib/lp/registry/browser/tests/test_product.py'
72--- lib/lp/registry/browser/tests/test_product.py 2012-11-15 16:03:56 +0000
73+++ lib/lp/registry/browser/tests/test_product.py 2012-11-23 14:31:20 +0000
74@@ -25,7 +25,6 @@
75 from lp.app.enums import (
76 InformationType,
77 PROPRIETARY_INFORMATION_TYPES,
78- PUBLIC_PROPRIETARY_INFORMATION_TYPES,
79 ServiceUsage,
80 )
81 from lp.registry.browser.product import (
82@@ -553,118 +552,6 @@
83 self.assertEqual(
84 InformationType.PUBLIC, updated_product.information_type)
85
86- def test_warning_on_public_artifacts(self):
87- # When changing to proprietary, any public artifacts will trigger a
88- # warning.
89- self.useFixture(FeatureFixture(
90- {u'disclosure.private_projects.enabled': u'on'}))
91- owner = self.factory.makePerson()
92- product = self.factory.makeProduct(owner=owner)
93- bug = self.factory.makeBug(target=product)
94-
95- def make_proprietary():
96- with person_logged_in(None):
97- browser = self.getViewBrowser(product, '+edit',
98- user=product.owner)
99- info_type = browser.getControl(name='field.information_type')
100- info_type.value = ['PROPRIETARY']
101- browser.getControl('Change').click()
102- with person_logged_in(owner):
103- product.information_type = InformationType.PUBLIC
104- return browser
105- browser = make_proprietary()
106-
107- def assertWarning(browser, text):
108- warning_tag = Tag('warning', 'div', text=text,
109- attrs={'class': 'warning message'})
110- self.assertThat(browser.contents, HTMLContains(warning_tag))
111- assertWarning(
112- browser, 'Some bugs are public. This project will not be fully '
113- 'Proprietary until they have been changed.')
114- browser = make_proprietary()
115- with person_logged_in(bug.owner):
116- bug.transitionToInformationType(InformationType.PROPRIETARY,
117- bug.owner)
118- browser = make_proprietary()
119- self.assertThat(browser.contents, Not(HTMLContains(
120- Tag('warning', 'div', attrs={'class': 'warning message'}))))
121-
122- def test_getPublicWarning_artifacts(self):
123- # The warning indicates what kinds of artifacts are public, in
124- # grammatical English.
125- owner = self.factory.makePerson()
126- product = self.factory.makeProduct(owner=owner)
127- bug = self.factory.makeBug(target=product)
128- view = create_initialized_view(product, '+edit')
129- with person_logged_in(owner):
130- product.information_type = InformationType.PROPRIETARY
131- warning = view.getPublicWarning(InformationType.PUBLIC)
132- self.assertEqual(
133- 'Some bugs are public. This project will not be fully '
134- 'Proprietary until they have been changed.', warning)
135- spec = self.factory.makeSpecification(product=product)
136- warning = view.getPublicWarning(InformationType.PUBLIC)
137- self.assertEqual(
138- 'Some bugs and blueprints are public. This project will not be '
139- 'fully Proprietary until they have been changed.', warning)
140- branch = self.factory.makeBranch(product=product, owner=owner,
141- information_type=InformationType.PUBLIC)
142- warning = view.getPublicWarning(InformationType.PUBLIC)
143- self.assertEqual(
144- 'Some bugs, branches, and blueprints are public. This project '
145- 'will not be fully Proprietary until they have been changed.',
146- warning)
147- with person_logged_in(owner):
148- bug.transitionToInformationType(
149- InformationType.PROPRIETARY, owner)
150- warning = view.getPublicWarning(InformationType.PUBLIC)
151- self.assertEqual(
152- 'Some branches and blueprints are public. This project '
153- 'will not be fully Proprietary until they have been changed.',
154- warning)
155- with person_logged_in(owner):
156- branch.transitionToInformationType(
157- InformationType.PROPRIETARY, owner)
158- warning = view.getPublicWarning(InformationType.PUBLIC)
159- self.assertEqual(
160- 'Some blueprints are public. This project will not be fully '
161- 'Proprietary until they have been changed.', warning)
162- with person_logged_in(owner):
163- spec.transitionToInformationType(
164- InformationType.PROPRIETARY, owner)
165- self.assertIs(None, view.getPublicWarning(InformationType.PUBLIC))
166-
167- def test_getPublicWarning_embargoed(self):
168- # The message reflects an Embargoed product type.
169- owner = self.factory.makePerson()
170- product = self.factory.makeProduct(owner=owner)
171- self.factory.makeBug(target=product)
172- with person_logged_in(owner):
173- product.information_type = InformationType.EMBARGOED
174- view = create_initialized_view(product, '+edit')
175- warning = view.getPublicWarning(InformationType.PUBLIC)
176- self.assertEqual(
177- 'Some bugs are public. This project will not be fully '
178- 'Embargoed until they have been changed.', warning)
179-
180- def test_getPublicWarning_transition(self):
181- # The warning is emitted only when transitioning from PUBLIC to a
182- # proprietary type.
183- owner = self.factory.makePerson()
184- product = self.factory.makeProduct(owner=owner)
185- self.factory.makeBug(target=product)
186- view = create_initialized_view(product, '+edit')
187- for from_type in PUBLIC_PROPRIETARY_INFORMATION_TYPES:
188- for to_type in PUBLIC_PROPRIETARY_INFORMATION_TYPES:
189- with person_logged_in(owner):
190- product.information_type = to_type
191- warning = view.getPublicWarning(from_type)
192- if (to_type in PROPRIETARY_INFORMATION_TYPES and
193- from_type == InformationType.PUBLIC):
194- self.assertIsNot(None, warning)
195- else:
196- self.assertIs(None, warning)
197-
198
199 class ProductSetReviewLicensesViewTestCase(TestCaseWithFactory):
200 """Tests the ProductSetReviewLicensesView."""
201
202=== modified file 'lib/lp/registry/model/product.py'
203--- lib/lp/registry/model/product.py 2012-11-19 18:23:06 +0000
204+++ lib/lp/registry/model/product.py 2012-11-23 14:31:20 +0000
205@@ -111,6 +111,7 @@
206 BUG_POLICY_DEFAULT_TYPES,
207 )
208 from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask
209+from lp.bugs.model.bug import Bug
210 from lp.bugs.model.bugtarget import (
211 BugTargetBase,
212 OfficialBugTagTargetMixin,
213@@ -460,27 +461,53 @@
214 if value in PROPRIETARY_INFORMATION_TYPES:
215 if self.answers_usage == ServiceUsage.LAUNCHPAD:
216 raise CannotChangeInformationType('Answers is enabled.')
217+ if self._SO_creating or value not in PROPRIETARY_INFORMATION_TYPES:
218+ return value
219+ # Additional checks when transitioning an existing product to a
220+ # proprietary type
221+ # All specs located by an ALL search are public.
222+ public_specs = self.specifications(
223+ None, filter=[SpecificationFilter.ALL])
224+ if not public_specs.is_empty():
225+ # Unlike bugs and branches, specifications cannot be USERDATA or a
226+ # security type.
227+ raise CannotChangeInformationType(
228+ 'Some blueprints are public.')
229+ non_proprietary_bugs = Store.of(self).find(Bug,
230+ Not(Bug.information_type.is_in(PROPRIETARY_INFORMATION_TYPES)),
231+ BugTask.bug == Bug.id, BugTask.product == self.id)
232+ if not non_proprietary_bugs.is_empty():
233+ raise CannotChangeInformationType(
234+ 'Some bugs are neither proprietary nor embargoed.')
235+ # Default returns all public branches.
236+ non_proprietary_branches = Store.of(self).find(
237+ Branch, Branch.product == self.id,
238+ Not(Branch.information_type.is_in(PROPRIETARY_INFORMATION_TYPES))
239+ )
240+ if not non_proprietary_branches.is_empty():
241+ raise CannotChangeInformationType(
242+ 'Some branches are neither proprietary nor embargoed.')
243+ if not self.packagings.is_empty():
244+ raise CannotChangeInformationType('Some series are packaged.')
245+ if self.translations_usage == ServiceUsage.LAUNCHPAD:
246+ raise CannotChangeInformationType('Translations are enabled.')
247 # Proprietary check works only after creation, because during
248 # creation, has_commercial_subscription cannot give the right value
249 # and triggers an inappropriate DB flush.
250
251 # If you're changing the license, and setting a PROPRIETARY
252- # information type, yet you don't have a subscription you get one when
253- # the license is set.
254+ # information type, yet you don't have a subscription, you get one
255+ # when the license is set.
256+
257+ # Create the complimentary commercial subscription for the product.
258+ self._ensure_complimentary_subscription()
259
260 # If you have a commercial subscription, but it's not current, you
261 # cannot set the information type to a PROPRIETARY type.
262- if not self._SO_creating and value in PROPRIETARY_INFORMATION_TYPES:
263- if not self.packagings.is_empty():
264- raise CannotChangeInformationType('Some series are packaged.')
265- # Create the complimentary commercial subscription for the product.
266- self._ensure_complimentary_subscription()
267-
268- if not self.has_current_commercial_subscription:
269- raise CommercialSubscribersOnly(
270- 'A valid commercial subscription is required for private'
271- ' Projects.')
272-
273+ if not self.has_current_commercial_subscription:
274+ raise CommercialSubscribersOnly(
275+ 'A valid commercial subscription is required for private'
276+ ' Projects.')
277 return value
278
279 _information_type = EnumCol(
280
281=== modified file 'lib/lp/registry/tests/test_product.py'
282--- lib/lp/registry/tests/test_product.py 2012-11-19 06:26:32 +0000
283+++ lib/lp/registry/tests/test_product.py 2012-11-23 14:31:20 +0000
284@@ -391,6 +391,63 @@
285 expected = [InformationType.USERDATA, InformationType.PRIVATESECURITY]
286 self.assertContentEqual(expected, [policy.type for policy in aps])
287
288+ def test_change_info_type_proprietary_check_artifacts(self):
289+ # Cannot change product information_type if any artifacts are public.
290+ spec_policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
291+ product = self.factory.makeProduct(
292+ licenses=[License.OTHER_PROPRIETARY],
293+ specification_sharing_policy=spec_policy,
294+ bug_sharing_policy=BugSharingPolicy.PUBLIC_OR_PROPRIETARY,
295+ branch_sharing_policy=BranchSharingPolicy.PUBLIC_OR_PROPRIETARY,
296+ )
297+ self.useContext(person_logged_in(product.owner))
298+ spec = self.factory.makeSpecification(product=product)
299+ for info_type in PROPRIETARY_INFORMATION_TYPES:
300+ with ExpectedException(
301+ CannotChangeInformationType,
302+ 'Some blueprints are public.'):
303+ product.information_type = info_type
304+ spec.transitionToInformationType(InformationType.PROPRIETARY,
305+ product.owner)
306+ bug = self.factory.makeBug(target=product)
307+ for bug_info_type in FREE_INFORMATION_TYPES:
308+ bug.transitionToInformationType(bug_info_type, product.owner)
309+ for info_type in PROPRIETARY_INFORMATION_TYPES:
310+ with ExpectedException(
311+ CannotChangeInformationType,
312+ 'Some bugs are neither proprietary nor embargoed.'):
313+ product.information_type = info_type
314+ bug.transitionToInformationType(InformationType.PROPRIETARY,
315+ product.owner)
316+ branch = self.factory.makeBranch(product=product)
317+ for branch_info_type in FREE_INFORMATION_TYPES:
318+ branch.transitionToInformationType(branch_info_type,
319+ product.owner)
320+ for info_type in PROPRIETARY_INFORMATION_TYPES:
321+ with ExpectedException(
322+ CannotChangeInformationType,
323+ 'Some branches are neither proprietary nor embargoed.'):
324+ product.information_type = info_type
325+ branch.transitionToInformationType(InformationType.PROPRIETARY,
326+ product.owner)
327+ for info_type in PROPRIETARY_INFORMATION_TYPES:
328+ product.information_type = info_type
329+
330+ def test_change_info_type_proprietary_check_translations(self):
331+ product = self.factory.makeProduct(
332+ licenses=[License.OTHER_PROPRIETARY])
333+ with person_logged_in(product.owner):
334+ for usage in ServiceUsage:
335+ product.translations_usage = usage.value
336+ for info_type in PROPRIETARY_INFORMATION_TYPES:
337+ if product.translations_usage == ServiceUsage.LAUNCHPAD:
338+ with ExpectedException(
339+ CannotChangeInformationType,
340+ 'Translations are enabled.'):
341+ product.information_type = info_type
342+ else:
343+ product.information_type = info_type
344+
345 def test_change_info_type_proprietary_sets_policies(self):
346 # Changing information type from public to proprietary sets the
347 # appropriate policies