13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
3 |
||
4 |
import logging |
|
5 |
import os |
|
6 |
import threading |
|
7 |
import urllib |
|
14158.2.1
by j.c.sackett
Checking for private status implemented. |
8 |
import urllib2 |
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
9 |
import urlparse |
10 |
import xmlrpclib |
|
11 |
||
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
12 |
from bzrlib import ( |
13 |
errors, |
|
14 |
lru_cache, |
|
15 |
urlutils, |
|
16 |
)
|
|
7675.668.1
by Michael Hudson
only call get_transport(internal_branch_by_id_root) once per thread |
17 |
from bzrlib.transport import get_transport |
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
18 |
from loggerhead.apps import ( |
19 |
favicon_app, |
|
20 |
static_app, |
|
21 |
)
|
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
22 |
from loggerhead.apps.branch import BranchWSGIApp |
13686.2.20
by Robert Collins
Migrate the launchpad_loggerhead implementation of oops_middleware to that within oops_wsgi. |
23 |
import oops_wsgi |
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
24 |
from openid.consumer.consumer import ( |
25 |
CANCEL, |
|
26 |
Consumer, |
|
27 |
FAILURE, |
|
28 |
SUCCESS, |
|
29 |
)
|
|
30 |
from openid.extensions.sreg import ( |
|
31 |
SRegRequest, |
|
32 |
SRegResponse, |
|
33 |
)
|
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
34 |
from paste.fileapp import DataApp |
13686.2.20
by Robert Collins
Migrate the launchpad_loggerhead implementation of oops_middleware to that within oops_wsgi. |
35 |
from paste.httpexceptions import ( |
36 |
HTTPMovedPermanently, |
|
37 |
HTTPNotFound, |
|
38 |
HTTPUnauthorized, |
|
39 |
)
|
|
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
40 |
from paste.request import ( |
41 |
construct_url, |
|
42 |
parse_querystring, |
|
43 |
path_info_pop, |
|
44 |
)
|
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
45 |
|
46 |
from canonical.config import config |
|
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
47 |
from canonical.launchpad.webapp.errorlog import ErrorReportingUtility |
48 |
from canonical.launchpad.webapp.vhosts import allvhosts |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
49 |
from canonical.launchpad.xmlrpc import faults |
50 |
from lp.code.interfaces.codehosting import ( |
|
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
51 |
BRANCH_TRANSPORT, |
52 |
LAUNCHPAD_ANONYMOUS, |
|
53 |
)
|
|
54 |
from lp.codehosting.safe_open import safe_open |
|
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
55 |
from lp.codehosting.vfs import get_lp_server |
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
56 |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
57 |
|
58 |
robots_txt = '''\ |
|
59 |
User-agent: *
|
|
60 |
Disallow: /
|
|
61 |
'''
|
|
62 |
||
63 |
robots_app = DataApp(robots_txt, content_type='text/plain') |
|
64 |
||
65 |
||
66 |
thread_transports = threading.local() |
|
67 |
||
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
68 |
|
69 |
def check_fault(fault, *fault_classes): |
|
70 |
"""Check if 'fault's faultCode matches any of 'fault_classes'.
|
|
71 |
||
72 |
:param fault: An instance of `xmlrpclib.Fault`.
|
|
73 |
:param fault_classes: Any number of `LaunchpadFault` subclasses.
|
|
74 |
"""
|
|
75 |
for cls in fault_classes: |
|
76 |
if fault.faultCode == cls.error_code: |
|
77 |
return True |
|
78 |
return False |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
79 |
|
80 |
||
81 |
class RootApp: |
|
82 |
||
83 |
def __init__(self, session_var): |
|
84 |
self.graph_cache = lru_cache.LRUCache(10) |
|
85 |
self.branchfs = xmlrpclib.ServerProxy( |
|
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
86 |
config.codehosting.codehosting_endpoint) |
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
87 |
self.session_var = session_var |
88 |
self.log = logging.getLogger('lp-loggerhead') |
|
89 |
||
7675.668.1
by Michael Hudson
only call get_transport(internal_branch_by_id_root) once per thread |
90 |
def get_transport(self): |
91 |
t = getattr(thread_transports, 'transport', None) |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
92 |
if t is None: |
7675.668.1
by Michael Hudson
only call get_transport(internal_branch_by_id_root) once per thread |
93 |
thread_transports.transport = get_transport( |
94 |
config.codehosting.internal_branch_by_id_root) |
|
95 |
return thread_transports.transport |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
96 |
|
97 |
def _make_consumer(self, environ): |
|
98 |
"""Build an OpenID `Consumer` object with standard arguments."""
|
|
12289.1.1
by William Grant
Stop loggerhead from using an OpenID store. If a store is used it must be shared between instances, and it's easier to just not use one. |
99 |
# Multiple instances need to share a store or not use one at all (in
|
100 |
# which case they will use check_authentication). Using no store is
|
|
101 |
# easier, and check_authentication is cheap.
|
|
102 |
return Consumer(environ[self.session_var], None) |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
103 |
|
104 |
def _begin_login(self, environ, start_response): |
|
105 |
"""Start the process of authenticating with OpenID.
|
|
106 |
||
107 |
We redirect the user to Launchpad to identify themselves, asking to be
|
|
108 |
sent their nickname. Launchpad will then redirect them to our +login
|
|
109 |
page with enough information that we can then redirect them again to
|
|
110 |
the page they were looking at, with a cookie that gives us the
|
|
111 |
username.
|
|
112 |
"""
|
|
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
113 |
openid_vhost = config.launchpad.openid_provider_vhost |
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
114 |
openid_request = self._make_consumer(environ).begin( |
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
115 |
allvhosts.configs[openid_vhost].rooturl) |
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
116 |
openid_request.addExtension( |
117 |
SRegRequest(required=['nickname'])) |
|
118 |
back_to = construct_url(environ) |
|
119 |
raise HTTPMovedPermanently(openid_request.redirectURL( |
|
120 |
config.codehosting.secure_codebrowse_root, |
|
121 |
config.codehosting.secure_codebrowse_root + '+login/?' |
|
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
122 |
+ urllib.urlencode({'back_to': back_to}))) |
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
123 |
|
124 |
def _complete_login(self, environ, start_response): |
|
125 |
"""Complete the OpenID authentication process.
|
|
126 |
||
127 |
Here we handle the result of the OpenID process. If the process
|
|
128 |
succeeded, we record the username in the session and redirect the user
|
|
129 |
to the page they were trying to view that triggered the login attempt.
|
|
130 |
In the various failures cases we return a 401 Unauthorized response
|
|
131 |
with a brief explanation of what went wrong.
|
|
132 |
"""
|
|
133 |
query = dict(parse_querystring(environ)) |
|
134 |
# Passing query['openid.return_to'] here is massive cheating, but
|
|
135 |
# given we control the endpoint who cares.
|
|
136 |
response = self._make_consumer(environ).complete( |
|
137 |
query, query['openid.return_to']) |
|
138 |
if response.status == SUCCESS: |
|
139 |
self.log.error('open id response: SUCCESS') |
|
140 |
sreg_info = SRegResponse.fromSuccessResponse(response) |
|
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
141 |
print sreg_info |
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
142 |
environ[self.session_var]['user'] = sreg_info['nickname'] |
143 |
raise HTTPMovedPermanently(query['back_to']) |
|
144 |
elif response.status == FAILURE: |
|
145 |
self.log.error('open id response: FAILURE: %s', response.message) |
|
146 |
exc = HTTPUnauthorized() |
|
147 |
exc.explanation = response.message |
|
148 |
raise exc |
|
149 |
elif response.status == CANCEL: |
|
150 |
self.log.error('open id response: CANCEL') |
|
151 |
exc = HTTPUnauthorized() |
|
7675.671.1
by Gary Poster
log out from bzr and openid after logging out from Launchpad. |
152 |
exc.explanation = "Authentication cancelled." |
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
153 |
raise exc |
154 |
else: |
|
155 |
self.log.error('open id response: UNKNOWN') |
|
156 |
exc = HTTPUnauthorized() |
|
157 |
exc.explanation = "Unknown OpenID response." |
|
158 |
raise exc |
|
159 |
||
7675.671.1
by Gary Poster
log out from bzr and openid after logging out from Launchpad. |
160 |
def _logout(self, environ, start_response): |
7675.672.2
by Brad Crittenden
Add documentation per review |
161 |
"""Logout of loggerhead.
|
162 |
||
163 |
Clear the cookie and redirect to `next_to`.
|
|
164 |
"""
|
|
7675.671.1
by Gary Poster
log out from bzr and openid after logging out from Launchpad. |
165 |
environ[self.session_var].clear() |
166 |
query = dict(parse_querystring(environ)) |
|
167 |
next_url = query.get('next_to') |
|
168 |
if next_url is None: |
|
169 |
next_url = allvhosts.configs['mainsite'].rooturl |
|
170 |
raise HTTPMovedPermanently(next_url) |
|
171 |
||
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
172 |
def __call__(self, environ, start_response): |
173 |
environ['loggerhead.static.url'] = environ['SCRIPT_NAME'] |
|
174 |
if environ['PATH_INFO'].startswith('/static/'): |
|
175 |
path_info_pop(environ) |
|
176 |
return static_app(environ, start_response) |
|
177 |
elif environ['PATH_INFO'] == '/favicon.ico': |
|
178 |
return favicon_app(environ, start_response) |
|
179 |
elif environ['PATH_INFO'] == '/robots.txt': |
|
180 |
return robots_app(environ, start_response) |
|
181 |
elif environ['PATH_INFO'].startswith('/+login'): |
|
182 |
return self._complete_login(environ, start_response) |
|
7675.671.1
by Gary Poster
log out from bzr and openid after logging out from Launchpad. |
183 |
elif environ['PATH_INFO'].startswith('/+logout'): |
184 |
return self._logout(environ, start_response) |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
185 |
path = environ['PATH_INFO'] |
186 |
trailingSlashCount = len(path) - len(path.rstrip('/')) |
|
187 |
user = environ[self.session_var].get('user', LAUNCHPAD_ANONYMOUS) |
|
7675.668.1
by Michael Hudson
only call get_transport(internal_branch_by_id_root) once per thread |
188 |
lp_server = get_lp_server(user, branch_transport=self.get_transport()) |
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
189 |
lp_server.start_server() |
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
190 |
try: |
14158.2.1
by j.c.sackett
Checking for private status implemented. |
191 |
|
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
192 |
try: |
193 |
transport_type, info, trail = self.branchfs.translatePath( |
|
194 |
user, urlutils.escape(path)) |
|
195 |
except xmlrpclib.Fault, f: |
|
196 |
if check_fault(f, faults.PathTranslationError): |
|
197 |
raise HTTPNotFound() |
|
198 |
elif check_fault(f, faults.PermissionDenied): |
|
199 |
# If we're not allowed to see the branch...
|
|
200 |
if environ['wsgi.url_scheme'] != 'https': |
|
201 |
# ... the request shouldn't have come in over http, as
|
|
202 |
# requests for private branches over http should be
|
|
203 |
# redirected to https by the dynamic rewrite script we
|
|
204 |
# use (which runs before this code is reached), but
|
|
205 |
# just in case...
|
|
206 |
env_copy = environ.copy() |
|
207 |
env_copy['wsgi.url_scheme'] = 'https' |
|
208 |
raise HTTPMovedPermanently(construct_url(env_copy)) |
|
209 |
elif user != LAUNCHPAD_ANONYMOUS: |
|
210 |
# ... if the user is already logged in and still can't
|
|
211 |
# see the branch, they lose.
|
|
212 |
exc = HTTPUnauthorized() |
|
213 |
exc.explanation = "You are logged in as %s." % user |
|
214 |
raise exc |
|
215 |
else: |
|
216 |
# ... otherwise, lets give them a chance to log in
|
|
217 |
# with OpenID.
|
|
218 |
return self._begin_login(environ, start_response) |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
219 |
else: |
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
220 |
raise
|
221 |
if transport_type != BRANCH_TRANSPORT: |
|
222 |
raise HTTPNotFound() |
|
223 |
trail = urlutils.unescape(trail).encode('utf-8') |
|
224 |
trail += trailingSlashCount * '/' |
|
225 |
amount_consumed = len(path) - len(trail) |
|
226 |
consumed = path[:amount_consumed] |
|
227 |
branch_name = consumed.strip('/') |
|
228 |
self.log.info('Using branch: %s', branch_name) |
|
229 |
if trail and not trail.startswith('/'): |
|
230 |
trail = '/' + trail |
|
231 |
environ['PATH_INFO'] = trail |
|
232 |
environ['SCRIPT_NAME'] += consumed.rstrip('/') |
|
233 |
branch_url = lp_server.get_url() + branch_name |
|
234 |
branch_link = urlparse.urljoin( |
|
235 |
config.codebrowse.launchpad_root, branch_name) |
|
236 |
cachepath = os.path.join( |
|
237 |
config.codebrowse.cachepath, branch_name[1:]) |
|
238 |
if not os.path.isdir(cachepath): |
|
239 |
os.makedirs(cachepath) |
|
240 |
self.log.info('branch_url: %s', branch_url) |
|
14301.2.1
by j.c.sackett
Fixed creation of api_url to look up branch status. |
241 |
base_api_url = config.appserver_root_url('api') |
242 |
branch_api_url = '%s/%s/%s' % ( |
|
243 |
base_api_url, |
|
244 |
'devel', |
|
245 |
branch_name, |
|
246 |
)
|
|
14158.2.3
by j.c.sackett
Updated try/except to base on error codes. |
247 |
self.log.info('branch_api_url: %s', branch_api_url) |
14158.2.1
by j.c.sackett
Checking for private status implemented. |
248 |
req = urllib2.Request(branch_api_url) |
14158.2.3
by j.c.sackett
Updated try/except to base on error codes. |
249 |
private = False |
14158.2.1
by j.c.sackett
Checking for private status implemented. |
250 |
try: |
251 |
# We need to determine if the branch is private
|
|
252 |
response = urllib2.urlopen(req) |
|
14158.2.3
by j.c.sackett
Updated try/except to base on error codes. |
253 |
except urllib2.HTTPError as response: |
14301.2.1
by j.c.sackett
Fixed creation of api_url to look up branch status. |
254 |
code = response.getcode() |
14399.2.1
by j.c.sackett
Added 404 to privacy implying error codes, since we mark many private things as not there rather than as forbidden. |
255 |
if code in (400, 401, 403, 404): |
256 |
# There are several error codes that imply private data.
|
|
257 |
# 400 (bad request) is a default error code from the API
|
|
258 |
# 401 (unauthorized) should never be returned as the
|
|
259 |
# requests are always from anon. If it is returned
|
|
260 |
# however, the data is certainly private.
|
|
261 |
# 403 (forbidden) is obviously private.
|
|
262 |
# 404 (not found) implies privacy from a private team or
|
|
263 |
# similar situation, which we hide as not existing rather
|
|
264 |
# than mark as forbidden.
|
|
14158.2.3
by j.c.sackett
Updated try/except to base on error codes. |
265 |
self.log.info("Branch is private") |
266 |
private = True |
|
267 |
self.log.info( |
|
268 |
"Branch state not determined; api error, return code: %s", |
|
14301.2.1
by j.c.sackett
Fixed creation of api_url to look up branch status. |
269 |
code) |
270 |
response.close() |
|
14158.2.1
by j.c.sackett
Checking for private status implemented. |
271 |
else: |
272 |
self.log.info("Branch is public") |
|
14301.2.1
by j.c.sackett
Fixed creation of api_url to look up branch status. |
273 |
response.close() |
14158.2.1
by j.c.sackett
Checking for private status implemented. |
274 |
|
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
275 |
try: |
276 |
bzr_branch = safe_open( |
|
7675.668.1
by Michael Hudson
only call get_transport(internal_branch_by_id_root) once per thread |
277 |
lp_server.get_url().strip(':/'), branch_url) |
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
278 |
except errors.NotBranchError, err: |
279 |
self.log.warning('Not a branch: %s', err) |
|
280 |
raise HTTPNotFound() |
|
281 |
bzr_branch.lock_read() |
|
282 |
try: |
|
283 |
view = BranchWSGIApp( |
|
284 |
bzr_branch, branch_name, {'cachepath': cachepath}, |
|
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
285 |
self.graph_cache, branch_link=branch_link, |
14158.2.4
by j.c.sackett
Vital step: passing in private. |
286 |
served_url=None, private=private) |
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
287 |
return view.app(environ, start_response) |
288 |
finally: |
|
289 |
bzr_branch.unlock() |
|
9590.1.135
by Michael Hudson
add files from launchpad-loggerhead tree to launchpad tree |
290 |
finally: |
9590.1.136
by Michael Hudson
reapply changes to less exotically constructed tree |
291 |
lp_server.stop_server() |
11225.1.1
by Andrew Bennetts
Add OOPS logging to codebrowse. |
292 |
|
293 |
||
294 |
def make_error_utility(): |
|
295 |
"""Make an error utility for logging errors from codebrowse."""
|
|
296 |
error_utility = ErrorReportingUtility() |
|
297 |
error_utility.configure('codebrowse') |
|
298 |
return error_utility |
|
299 |
||
300 |
||
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
301 |
# XXX AndrewBennets 2010-07-27: This HTML template should be replaced
|
302 |
# with the same one that lpnet uses for reporting OOPSes to users, or at
|
|
303 |
# least something that looks similar. But even this is better than the
|
|
304 |
# "Internal Server Error" you'd get otherwise.
|
|
11225.1.1
by Andrew Bennetts
Add OOPS logging to codebrowse. |
305 |
_oops_html_template = '''\ |
306 |
<html>
|
|
13757.1.1
by William Grant
Fix launchpad_loggerhead OOPS page template to use 'id' instead of 'oopsid' as a dict key. |
307 |
<head><title>Oops! %(id)s</title></head> |
11225.1.1
by Andrew Bennetts
Add OOPS logging to codebrowse. |
308 |
<body>
|
309 |
<h1>Oops!</h1>
|
|
310 |
<p>Something broke while generating the page.
|
|
311 |
Please try again in a few minutes, and if the problem persists file a bug at
|
|
12094.1.1
by Ian Booth
Removed old launchpad project references in bug urls |
312 |
<a href="https://bugs.launchpad.net/launchpad"
|
313 |
>https://bugs.launchpad.net/launchpad</a>
|
|
13757.1.1
by William Grant
Fix launchpad_loggerhead OOPS page template to use 'id' instead of 'oopsid' as a dict key. |
314 |
and quote OOPS-ID <strong>%(id)s</strong> |
11225.1.1
by Andrew Bennetts
Add OOPS logging to codebrowse. |
315 |
</p></body></html>'''
|
316 |
||
317 |
||
318 |
def oops_middleware(app): |
|
319 |
"""Middleware to log an OOPS if the request fails.
|
|
320 |
||
13811.2.1
by Jeroen Vermeulen
Fix some of the lint people left in the past few days. |
321 |
If the request fails before the response body has started then this
|
322 |
returns a basic HTML error page with the OOPS ID to the user (and status
|
|
323 |
code 500).
|
|
11225.1.1
by Andrew Bennetts
Add OOPS logging to codebrowse. |
324 |
"""
|
325 |
error_utility = make_error_utility() |
|
13686.2.20
by Robert Collins
Migrate the launchpad_loggerhead implementation of oops_middleware to that within oops_wsgi. |
326 |
return oops_wsgi.make_app(app, error_utility._oops_config, |
14452.4.2
by Robert Collins
Generate OOPS from bazaar.launchpad.net on slow responses (7 seconds or more). |
327 |
template=_oops_html_template, soft_start_timeout=7000) |