Merge lp:~jcsackett/launchpad/blueprint-private-traversal-deptree into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 16333
Proposed branch: lp:~jcsackett/launchpad/blueprint-private-traversal-deptree
Merge into: lp:launchpad
Prerequisite: lp:~jcsackett/launchpad/blueprint-private-traversal
Diff against target: 240 lines (+134/-12)
4 files modified
lib/lp/blueprints/browser/configure.zcml (+10/-6)
lib/lp/blueprints/browser/specificationdependency.py (+39/-0)
lib/lp/blueprints/browser/tests/test_specificationdependency.py (+79/-0)
lib/lp/blueprints/templates/specification-deptree.pt (+6/-6)
To merge this branch: bzr merge lp:~jcsackett/launchpad/blueprint-private-traversal-deptree
Reviewer Review Type Date Requested Status
Richard Harding (community) Approve
Review via email: mp+137390@code.launchpad.net

Commit message

Adjusts Specification:+deptree to filter for private specs.

Description of the change

Summary
=======
Much as with the SVG graph in Specification:+index, Specification:+deptree
is not privacy aware and 403s when a person who can't see a dependency or
blocked specification tries to access it. It should, as with the graph, merely
remove the specs the user doesn't have permission for.

Preimp
======
None--extension of previous work.

Implementation
==============
The deptree view has been updated to use the filtered all_deps and all_blocked
methods created in the branch this branch depends on. It has also had new
methods added, "dependencies" and "blocked_specs" that filter the attributes
of the same name from the context. As these are all used multiple times in the
template, they are cachedproperties.

The blocked_specs and dependency portlets used on this page are also adapted
to accept the view rather than the context, so that they can use the new view
methods.

Tests
=====
bin/test -vvct TestDepTree

QA
==
Ensure Specification:+deptree loads appropriately in situations of mixed
blueprint visibilities.

LoC
===
Part of private projects.

Lint
====

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/blueprints/templates/specification-deptree.pt
  lib/lp/blueprints/browser/tests/test_specificationdependency.py
  lib/lp/blueprints/doc/specgraph.txt
  lib/lp/blueprints/model/tests/test_specification.py
  lib/lp/blueprints/model/specification.py
  lib/lp/blueprints/browser/specificationdependency.py
  lib/lp/blueprints/browser/configure.zcml
  lib/lp/blueprints/browser/specification.py
  lib/lp/blueprints/interfaces/specification.py

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

Looks ok to me. As someone not familiar, is there something that tests that if you try to pull up where the root itself is non-public?

review: Approve
Revision history for this message
j.c.sackett (jcsackett) wrote :

> Looks ok to me. As someone not familiar, is there something that tests that if
> you try to pull up where the root itself is non-public?

If you're trying to pull up the root the usual 404 for a spec you can't see occurs, based on our standard privacy rules. See bug #1076477 and the linked branch.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/blueprints/browser/configure.zcml'
2--- lib/lp/blueprints/browser/configure.zcml 2012-10-15 04:59:17 +0000
3+++ lib/lp/blueprints/browser/configure.zcml 2012-12-02 05:08:20 +0000
4@@ -268,14 +268,18 @@
5 <browser:page
6 name="+listing-compact"
7 template="../templates/specification-listing-compact.pt"/>
8- <browser:page
9- name="+portlet-dependencies"
10- template="../templates/specification-portlet-dependencies.pt"/>
11- <browser:page
12- name="+portlet-blocked"
13- template="../templates/specification-portlet-blocked.pt"/>
14 </browser:pages>
15 <browser:page
16+ for="lp.blueprints.browser.specificationdependency.SpecificationDependencyTreeView"
17+ name="+portlet-dependencies"
18+ template="../templates/specification-portlet-dependencies.pt"
19+ permission="zope.Public"/>
20+ <browser:page
21+ for="lp.blueprints.browser.specificationdependency.SpecificationDependencyTreeView"
22+ name="+portlet-blocked"
23+ template="../templates/specification-portlet-blocked.pt"
24+ permission="zope.Public"/>
25+ <browser:page
26 name="+secrecy"
27 for="lp.blueprints.interfaces.specification.ISpecification"
28 class="lp.blueprints.browser.specification.SpecificationInformationTypeEditView"
29
30=== modified file 'lib/lp/blueprints/browser/specificationdependency.py'
31--- lib/lp/blueprints/browser/specificationdependency.py 2012-01-01 02:58:52 +0000
32+++ lib/lp/blueprints/browser/specificationdependency.py 2012-12-02 05:08:20 +0000
33@@ -12,17 +12,21 @@
34 ]
35
36 from lazr.restful.interface import copy_field
37+from zope.component import getUtility
38 from zope.interface import Interface
39
40 from lp import _
41+from lp.app.enums import InformationType
42 from lp.app.browser.launchpadform import (
43 action,
44 LaunchpadFormView,
45 )
46+from lp.app.interfaces.services import IService
47 from lp.blueprints.interfaces.specificationdependency import (
48 ISpecificationDependency,
49 ISpecificationDependencyRemoval,
50 )
51+from lp.services.propertycache import cachedproperty
52 from lp.services.webapp import (
53 canonical_url,
54 LaunchpadView,
55@@ -92,8 +96,43 @@
56
57
58 class SpecificationDependencyTreeView(LaunchpadView):
59+
60 label = "Blueprint dependency tree"
61
62+ def __init__(self, *args, **kwargs):
63+ super(SpecificationDependencyTreeView, self).__init__(*args, **kwargs)
64+ self.service = getUtility(IService, 'sharing')
65+
66 @property
67 def page_title(self):
68 return self.label
69+
70+ @cachedproperty
71+ def all_blocked(self):
72+ return self.context.all_blocked(self.user)
73+
74+ @cachedproperty
75+ def all_deps(self):
76+ return self.context.all_deps(self.user)
77+
78+ @cachedproperty
79+ def dependencies(self):
80+ deps = list(self.context.dependencies)
81+ if self.user:
82+ (ignore, ignore, deps) = self.service.getVisibleArtifacts(
83+ self.user, specifications=deps)
84+ else:
85+ deps = [d for d in deps if
86+ d.information_type == InformationType.PUBLIC]
87+ return deps
88+
89+ @cachedproperty
90+ def blocked_specs(self):
91+ blocked = list(self.context.blocked_specs)
92+ if self.user:
93+ (ignore, ignore, blocked) = self.service.getVisibleArtifacts(
94+ self.user, specifications=blocked)
95+ else:
96+ blocked = [b for b in blocked if
97+ b.information_type == InformationType.PUBLIC]
98+ return blocked
99
100=== modified file 'lib/lp/blueprints/browser/tests/test_specificationdependency.py'
101--- lib/lp/blueprints/browser/tests/test_specificationdependency.py 2012-08-20 16:38:10 +0000
102+++ lib/lp/blueprints/browser/tests/test_specificationdependency.py 2012-12-02 05:08:20 +0000
103@@ -8,15 +8,21 @@
104
105 __metaclass__ = type
106
107+from lp.app.enums import InformationType
108+from lp.registry.enums import SpecificationSharingPolicy
109 from lp.services.webapp import canonical_url
110 from lp.testing import (
111+ anonymous_logged_in,
112 BrowserTestCase,
113 person_logged_in,
114+ TestCaseWithFactory,
115 )
116 from lp.testing.layers import DatabaseFunctionalLayer
117+from lp.testing.views import create_view
118
119
120 class TestAddDependency(BrowserTestCase):
121+
122 layer = DatabaseFunctionalLayer
123
124 def test_add_dependency_by_url(self):
125@@ -35,3 +41,76 @@
126 # on ISpecification objects.
127 with person_logged_in(None):
128 self.assertIn(dependency, spec.dependencies)
129+
130+
131+class TestDepTree(TestCaseWithFactory):
132+
133+ layer = DatabaseFunctionalLayer
134+
135+ def test_deptree_filters_dependencies(self):
136+ # dep tree's blocked_specs and dependencies attributes filter
137+ # blueprints the user can't see.
138+ sharing_policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
139+ owner = self.factory.makePerson()
140+ product = self.factory.makeProduct(
141+ owner=owner, specification_sharing_policy=sharing_policy)
142+ root = self.factory.makeBlueprint(product=product)
143+ proprietary_dep = self.factory.makeBlueprint(
144+ product=product, information_type=InformationType.PROPRIETARY)
145+ public_dep = self.factory.makeBlueprint(product=product)
146+ root.createDependency(proprietary_dep)
147+ root.createDependency(public_dep)
148+
149+ # Anonymous can see only the public
150+ with anonymous_logged_in():
151+ view = create_view(root, name="+deptree")
152+ self.assertEqual([public_dep], view.all_deps)
153+ self.assertEqual([public_dep], view.dependencies)
154+
155+ # The owner can see everything.
156+ with person_logged_in(owner):
157+ view = create_view(root, name="+deptree")
158+ self.assertEqual(
159+ [proprietary_dep, public_dep], view.all_deps)
160+ self.assertEqual(
161+ [proprietary_dep, public_dep], view.dependencies)
162+
163+ # A random person cannot see the propriety dep.
164+ with person_logged_in(self.factory.makePerson()):
165+ view = create_view(root, name="+deptree")
166+ self.assertEqual([public_dep], view.all_deps)
167+ self.assertEqual([public_dep], view.dependencies)
168+
169+ def test_deptree_filters_blocked(self):
170+ # dep tree's blocked_specs and dependencies attributes filter
171+ # blueprints the user can't see.
172+ sharing_policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
173+ owner = self.factory.makePerson()
174+ product = self.factory.makeProduct(
175+ owner=owner, specification_sharing_policy=sharing_policy)
176+ root = self.factory.makeBlueprint(product=product)
177+ proprietary_blocked = self.factory.makeBlueprint(
178+ product=product, information_type=InformationType.PROPRIETARY)
179+ public_blocked = self.factory.makeBlueprint(product=product)
180+ proprietary_blocked.createDependency(root)
181+ public_blocked.createDependency(root)
182+
183+ # Anonymous can see only the public
184+ with anonymous_logged_in():
185+ view = create_view(root, name="+deptree")
186+ self.assertEqual([public_blocked], view.all_blocked)
187+ self.assertEqual([public_blocked], view.blocked_specs)
188+
189+ # The owner can see everything.
190+ with person_logged_in(owner):
191+ view = create_view(root, name="+deptree")
192+ self.assertEqual(
193+ [proprietary_blocked, public_blocked], view.all_blocked)
194+ self.assertEqual(
195+ [proprietary_blocked, public_blocked], view.blocked_specs)
196+
197+ # A random person cannot see the propriety dep.
198+ with person_logged_in(self.factory.makePerson()):
199+ view = create_view(root, name="+deptree")
200+ self.assertEqual([public_blocked], view.all_blocked)
201+ self.assertEqual([public_blocked], view.blocked_specs)
202
203=== modified file 'lib/lp/blueprints/templates/specification-deptree.pt'
204--- lib/lp/blueprints/templates/specification-deptree.pt 2009-09-22 11:35:39 +0000
205+++ lib/lp/blueprints/templates/specification-deptree.pt 2012-12-02 05:08:20 +0000
206@@ -10,8 +10,8 @@
207 <body>
208
209 <div metal:fill-slot="side">
210- <div tal:replace="structure context/@@+portlet-blocked" />
211- <div tal:replace="structure context/@@+portlet-dependencies" />
212+ <div tal:replace="structure view/@@+portlet-blocked" />
213+ <div tal:replace="structure view/@@+portlet-dependencies" />
214 </div>
215
216 <div metal:fill-slot="main">
217@@ -25,9 +25,9 @@
218
219 <div class="portlet">
220 <h2>Blueprints that must be implemented first</h2>
221- <div tal:repeat="spec context/all_deps"
222+ <div tal:repeat="spec view/all_deps"
223 tal:replace="structure spec/@@+listing-detailed" />
224- <p tal:condition="not: context/dependencies">
225+ <p tal:condition="not: view/dependencies">
226 <i>None - this blueprint does not depend on any others.</i>
227 </p>
228 </div>
229@@ -39,9 +39,9 @@
230
231 <div class="portlet">
232 <h2>Blueprints that can then be implemented</h2>
233- <div tal:repeat="spec context/all_blocked"
234+ <div tal:repeat="spec view/all_blocked"
235 tal:replace="structure spec/@@+listing-detailed" />
236- <p tal:condition="not: context/blocked_specs">
237+ <p tal:condition="not: view/blocked_specs">
238 No blueprints depend on this one.
239 </p>
240 </div>