Merge lp:~wallyworld/launchpad/no-private-bug-rss into lp:launchpad

Proposed by Ian Booth
Status: Superseded
Proposed branch: lp:~wallyworld/launchpad/no-private-bug-rss
Merge into: lp:launchpad
Diff against target: 234 lines (+108/-8)
4 files modified
lib/canonical/launchpad/browser/feeds.py (+24/-1)
lib/canonical/launchpad/pagetests/feeds/xx-links.txt (+52/-0)
lib/lp/bugs/feed/bug.py (+13/-4)
lib/lp/code/feed/branch.py (+19/-3)
To merge this branch: bzr merge lp:~wallyworld/launchpad/no-private-bug-rss
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+61572@code.launchpad.net

This proposal has been superseded by a proposal from 2011-05-20.

Commit message

[r=allenap][bug=302097] Stop exposing rss feeds for private bugs or branches.

Description of the change

This branch stops exposing rss feeds for private bugs or branches.

== Implementation ==

Add a class method feed_allowed() to FeedLinkBase, default to returning True. However, this is overridden in BranchFeedLink and BugFeedLink to return true only if the branch/bug is not private.

TODO: when a user uses the inline ajax widget to change the privacy status, the rss feed status is not toggled. This will be done in a subsequent branch.

== Tests ==

Extends the xx-links.txt doctest to check that private bugs and branches pages do not render the rss link element.

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/canonical/launchpad/browser/feeds.py
  lib/canonical/launchpad/pagetests/feeds/xx-links.txt

./lib/canonical/launchpad/pagetests/feeds/xx-links.txt
       1: narrative uses a moin header.
       9: narrative uses a moin header.
      33: narrative uses a moin header.
      58: narrative uses a moin header.
      88: narrative uses a moin header.
     122: narrative uses a moin header.
     149: want exceeds 78 characters.
     152: want exceeds 78 characters.
     155: want exceeds 78 characters.
     158: want exceeds 78 characters.
     160: narrative uses a moin header.
     208: narrative uses a moin header.
     235: narrative uses a moin header.
     248: narrative uses a moin header.
     261: narrative uses a moin header.
     270: want exceeds 78 characters.
     274: narrative uses a moin header.
     290: narrative uses a moin header.
     306: narrative uses a moin header.
     322: narrative uses a moin header.
     332: want exceeds 78 characters.
     333: want exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/feeds.py'
2--- lib/canonical/launchpad/browser/feeds.py 2010-11-08 12:52:43 +0000
3+++ lib/canonical/launchpad/browser/feeds.py 2011-05-20 08:58:39 +0000
4@@ -177,6 +177,14 @@
5 "Context %r does not provide interface %r"
6 % (context, self.usedfor))
7
8+ @classmethod
9+ def feed_allowed(cls, context):
10+ """Return True if a feed is allowed for the given context.
11+
12+ Subclasses should override this method as necessary.
13+ """
14+ return True
15+
16
17 class BugFeedLink(FeedLinkBase):
18 usedfor = IBugTask
19@@ -190,6 +198,12 @@
20 return urlappend(self.rooturl,
21 'bugs/' + str(self.context.bug.id) + '/bug.atom')
22
23+ @classmethod
24+ def feed_allowed(cls, context):
25+ """See `FeedLinkBase`"""
26+ # No feeds for private bugs.
27+ return not context.bug.private
28+
29
30 class BugTargetLatestBugsFeedLink(FeedLinkBase):
31 usedfor = IHasBugs
32@@ -222,6 +236,7 @@
33 return urlappend(canonical_url(self.context, rootsite='feeds'),
34 'announcements.atom')
35
36+
37 class RootAnnouncementsFeedLink(AnnouncementsFeedLink):
38 usedfor = ILaunchpadRoot
39
40@@ -296,11 +311,18 @@
41 @property
42 def title(self):
43 return 'Latest Revisions for Branch %s' % self.context.displayname
44+
45 @property
46 def href(self):
47 return urlappend(canonical_url(self.context, rootsite="feeds"),
48 'branch.atom')
49
50+ @classmethod
51+ def feed_allowed(cls, context):
52+ """See `FeedLinkBase`"""
53+ # No feeds for private branches.
54+ return not context.private
55+
56
57 class PersonRevisionsFeedLink(FeedLinkBase):
58 """Feed links for revisions created by a person."""
59@@ -346,4 +368,5 @@
60 def feed_links(self):
61 return [feed_type(self.context)
62 for feed_type in self.feed_types
63- if feed_type.usedfor.providedBy(self.context)]
64+ if feed_type.usedfor.providedBy(self.context) and
65+ feed_type.feed_allowed(self.context)]
66
67=== modified file 'lib/canonical/launchpad/pagetests/feeds/xx-links.txt'
68--- lib/canonical/launchpad/pagetests/feeds/xx-links.txt 2011-05-05 05:46:41 +0000
69+++ lib/canonical/launchpad/pagetests/feeds/xx-links.txt 2011-05-20 08:58:39 +0000
70@@ -42,6 +42,37 @@
71 href="http://feeds.launchpad.dev/bugs/1/bug.atom"
72 title="Bug 1 Feed" />]
73
74+But if the bug is private, there should be no link.
75+
76+ >>> from canonical.launchpad.testing.pages import setupBrowserForUser
77+ >>> from zope.component import getUtility
78+ >>> from lp.registry.interfaces.person import IPersonSet
79+ >>> login(ANONYMOUS)
80+ >>> user = getUtility(IPersonSet).getByEmail('daf@canonical.com')
81+ >>> logout()
82+ >>> auth_browser = setupBrowserForUser(user, 'daf')
83+ >>> auth_browser.open('http://bugs.launchpad.dev/jokosher/+bug/14')
84+ >>> soup = BeautifulSoup(auth_browser.contents)
85+ >>> soup.head.findAll('link', type='application/atom+xml')
86+ []
87+
88+But if they somehow manage to hack the url or use inline ajax editing of the
89+bug status and attempt to subscribe, they are redirected to the bug page:
90+
91+ >>> def print_notifications(browser):
92+ ... tags = find_tags_by_class(
93+ ... browser.contents, 'error message')
94+ ... if len(tags) == 0:
95+ ... print 'No messages.'
96+ ... else:
97+ ... for tag in tags:
98+ ... print extract_text(tag)
99+
100+ >>> auth_browser.open('http://feeds.launchpad.dev/bugs/14/bug.atom')
101+ >>> print auth_browser.url
102+ http://bugs.launchpad.dev/jokosher/+bug/14
103+ >>> print_notifications(auth_browser)
104+ Feeds do not serve private bugs.
105
106 == Latest Bugs and Branches for a Person ==
107
108@@ -319,3 +350,24 @@
109 [<link rel="alternate" type="application/atom+xml"
110 href="http://feeds.launchpad.dev/~mark/firefox/release--0.9.1/branch.atom"
111 title="Latest Revisions for Branch lp://dev/~mark/firefox/release--0.9.1" />]
112+
113+But if the branch is private, there should be no link.
114+
115+ >>> login(ANONYMOUS)
116+ >>> user = getUtility(IPersonSet).getByEmail('test@canonical.com')
117+ >>> logout()
118+ >>> auth_browser = setupBrowserForUser(user)
119+ >>> auth_browser.open(
120+ ... 'https://code.launchpad.dev/~name12/landscape/feature-x')
121+ >>> soup = BeautifulSoup(auth_browser.contents)
122+ >>> soup.head.findAll('link', type='application/atom+xml')
123+ []
124+
125+But if they somehow manage to hack the url, they are redirected to a page with
126+an error notification:
127+
128+ >>> browser.open('http://feeds.launchpad.dev/~name12/landscape/feature-x/branch.atom')
129+ >>> print browser.url
130+ http://code.launchpad.dev/
131+ >>> print_notifications(browser)
132+ Feeds do not serve private branches.
133
134=== modified file 'lib/lp/bugs/feed/bug.py'
135--- lib/lp/bugs/feed/bug.py 2010-08-20 20:31:18 +0000
136+++ lib/lp/bugs/feed/bug.py 2011-05-20 08:58:39 +0000
137@@ -14,13 +14,14 @@
138
139 from z3c.ptcompat import ViewPageTemplateFile
140 from zope.component import getUtility
141-from zope.security.interfaces import Unauthorized
142
143 from canonical.config import config
144 from canonical.launchpad.webapp import (
145 canonical_url,
146 urlparse,
147 )
148+from canonical.launchpad.webapp.authorization import check_permission
149+from canonical.launchpad.webapp.interfaces import ILaunchpadRoot
150 from canonical.launchpad.webapp.publisher import LaunchpadView
151 from canonical.lazr.feed import (
152 FeedBase,
153@@ -141,8 +142,8 @@
154 def getBugsFromBugTasks(self, tasks):
155 """Given a list of BugTasks return the list of associated bugs.
156
157- Since a Bug can have multiple BugTasks, we only select bugs that have not
158- yet been seen.
159+ Since a Bug can have multiple BugTasks, we only select bugs that have
160+ not yet been seen.
161 """
162 bug_ids = []
163 for task in tasks:
164@@ -171,7 +172,15 @@
165 # For a `BugFeed` we must ensure that the bug is not private.
166 super(BugFeed, self).initialize()
167 if self.context.private:
168- raise Unauthorized("Feeds do not serve private bugs")
169+ self.request.response.addErrorNotification(
170+ "Feeds do not serve private bugs.")
171+ if check_permission("launchpad.View", self.context):
172+ self.request.response.redirect(canonical_url(self.context))
173+ else:
174+ # Bug cannot be seen so redirect to the bugs index page.
175+ root = getUtility(ILaunchpadRoot)
176+ self.request.response.redirect(
177+ canonical_url(root, rootsite='bugs'))
178
179 @property
180 def title(self):
181
182=== modified file 'lib/lp/code/feed/branch.py'
183--- lib/lp/code/feed/branch.py 2011-03-03 01:13:47 +0000
184+++ lib/lp/code/feed/branch.py 2011-05-20 08:58:39 +0000
185@@ -30,6 +30,7 @@
186 LaunchpadView,
187 urlparse,
188 )
189+from canonical.launchpad.webapp.interfaces import ILaunchpadRoot
190 from canonical.lazr.feed import (
191 FeedBase,
192 FeedEntry,
193@@ -74,6 +75,7 @@
194 super(BranchFeedContentView, self).__init__(context, request)
195 self.feed = feed
196 self.template_ = template
197+
198 def render(self):
199 """Render the view."""
200 return ViewPageTemplateFile(self.template_)(self)
201@@ -375,7 +377,7 @@
202
203 def __init__(self, person, rootsite):
204
205- no_email = person.name_without_email
206+ no_email = person.name_without_email
207 if no_email:
208 self.name = no_email
209 else:
210@@ -400,8 +402,22 @@
211 """See `IFeed`."""
212 # For a `BranchFeed` we must ensure that the branch is not private.
213 super(BranchFeed, self).initialize()
214- if self.context.private:
215- raise Unauthorized("Feeds do not serve private branches")
216+ try:
217+ feed_allowed = not self.context.private
218+ if not feed_allowed:
219+ # We are logged in and can see the branch so redirect to the
220+ # branch index page.
221+ redirect_url = canonical_url(self.context)
222+ except Unauthorized:
223+ # Branch cannot be seen so redirect to the code index page.
224+ feed_allowed = False
225+ root = getUtility(ILaunchpadRoot)
226+ redirect_url = canonical_url(root, rootsite='code')
227+
228+ if not feed_allowed:
229+ self.request.response.addErrorNotification(
230+ "Feeds do not serve private branches.")
231+ self.request.response.redirect(redirect_url)
232
233 @property
234 def title(self):