~launchpad-pqm/launchpad/devel

14080.3.14 by Gavin Panella
Update copyright.
1
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
8687.15.18 by Karl Fogel
Add the copyright header block to files under lib/canonical/.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
10512.1.2 by Guilherme Salgado
Remove some lint
4
# pylint: disable-msg=W0231,E1002
4823.6.7 by Barry Warsaw
Much simplification and improvement in the way private ports are done.
5
2438 by Canonical.com Patch Queue Manager
r=spiv new tests for some menu system functionality. reorganisation of browser publication code.
6
"""Definition of the internet servers that Launchpad uses."""
7
8
__metaclass__ = type
9
7666.3.5 by Michael Nelson
Added query_string_params to LaunchpadBrowserRequest as suggested by Gary.
10
import cgi
3691.3.2 by Steve Alexander
set up new host header based virtual hosting, make canonical_url able to be configured with a rootsite, removal of unused IDefaultViewDirective, fix race condition in request publication factory.
11
import threading
4246.2.1 by James Henstridge
catch non-Fault exceptions in XML-RPC and display the OOPS code
12
import xmlrpclib
3691.3.2 by Steve Alexander
set up new host header based virtual hosting, make canonical_url able to be configured with a rootsite, removal of unused IDefaultViewDirective, fix race condition in request publication factory.
13
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
14
from lazr.restful.interfaces import (
11351.1.3 by Diogo Matsubara
Merge from trunk, fix 1 conflict on servers.py
15
    ICollectionResource,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
16
    IWebServiceConfiguration,
17
    IWebServiceVersion,
18
    )
19
from lazr.restful.publisher import (
20
    WebServicePublicationMixin,
21
    WebServiceRequestTraversal,
22
    )
23
from lazr.uri import URI
7055.2.15 by Michael Hudson
clear the cache on commit()
24
import transaction
25
from transaction.interfaces import ISynchronizer
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
26
from zc.zservertracelog.tracelog import Server as ZServerTracelogServer
27
from zope.app.form.browser.itemswidgets import MultiDataHelper
4311.2.2 by Francis J. Lacoste
Show how the regular widgets fail. Allow uninstallation of the monkey patch.
28
from zope.app.form.browser.widget import SimpleInputWidget
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
29
from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
3673.5.2 by Bjorn Tillenius
convert the xmlrpc server to WSGIHTTPServer.
30
from zope.app.publication.interfaces import IRequestPublicationFactory
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
31
from zope.app.publication.requestpublicationregistry import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
32
    factoryRegistry as publisher_factory_registry,
33
    )
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
34
from zope.app.server import wsgi
35
from zope.app.wsgi import WSGIPublisherApplication
7893.3.4 by Francis J. Lacoste
Moved generic webservice publisher stuff to canonical.lazr.rest.publisher.
36
from zope.component import getUtility
14080.3.9 by Gavin Panella
Demonstrate the FinishReadOnlyRequestEvent is fired by both LaunchpadBrowserPublication and WebServicePublication.
37
from zope.event import notify
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
38
from zope.interface import (
39
    alsoProvides,
40
    implements,
41
    )
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
42
from zope.publisher.browser import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
43
    BrowserRequest,
44
    BrowserResponse,
45
    TestRequest,
46
    )
5206.2.1 by Leonard Richardson
Added test and fix.
47
from zope.publisher.interfaces import NotFound
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
48
from zope.publisher.xmlrpc import (
49
    XMLRPCRequest,
50
    XMLRPCResponse,
51
    )
11579.1.1 by Leonard Richardson
Reverted my earlier change.
52
from zope.security.interfaces import (
53
    IParticipation,
54
    Unauthorized,
55
    )
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
56
from zope.security.proxy import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
57
    isinstance as zope_isinstance,
58
    removeSecurityProxy,
59
    )
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
60
from zope.server.http.commonaccesslogger import CommonAccessLogger
7362.11.64 by Jonathan Lange
Clean up lint
61
from zope.server.http.wsgihttpserver import PMDBWSGIHTTPServer
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
62
from zope.session.interfaces import ISession
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
63
14606.4.3 by William Grant
Move the interfaces across too.
64
from lp.app.errors import UnexpectedFormData
65
import lp.layers
14605.1.1 by Curtis Hovey
Moved canonical.config to lp.services.
66
from lp.services.config import config
14606.4.3 by William Grant
Move the interfaces across too.
67
from lp.services.features import get_relevant_feature_controller
68
from lp.services.features.flags import NullFeatureController
69
from lp.services.feeds.interfaces.application import IFeedsApplication
70
from lp.services.feeds.interfaces.feed import IFeed
71
from lp.services.oauth.interfaces import (
72
    IOAuthConsumerSet,
73
    IOAuthSignedRequest,
74
    TokenException,
75
    )
76
from lp.services.propertycache import cachedproperty
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
77
from lp.services.webapp.authentication import (
11579.1.1 by Leonard Richardson
Reverted my earlier change.
78
    check_oauth_signature,
79
    get_oauth_authorization,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
80
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
81
from lp.services.webapp.authorization import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
82
    LAUNCHPAD_SECURITY_POLICY_CACHE_KEY,
83
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
84
from lp.services.webapp.errorlog import ErrorReportRequest
85
from lp.services.webapp.interfaces import (
14080.3.9 by Gavin Panella
Demonstrate the FinishReadOnlyRequestEvent is fired by both LaunchpadBrowserPublication and WebServicePublication.
86
    FinishReadOnlyRequestEvent,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
87
    IAPIDocRoot,
88
    IBasicLaunchpadRequest,
89
    IBrowserFormNG,
90
    ILaunchpadBrowserApplicationRequest,
91
    ILaunchpadProtocolError,
92
    INotificationRequest,
93
    INotificationResponse,
94
    IPlacelessAuthUtility,
11579.1.1 by Leonard Richardson
Reverted my earlier change.
95
    IPlacelessLoginSource,
96
    OAuthPermission,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
97
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
98
from lp.services.webapp.notifications import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
99
    NotificationList,
100
    NotificationRequest,
101
    NotificationResponse,
102
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
103
from lp.services.webapp.opstats import OpStats
104
from lp.services.webapp.publication import LaunchpadBrowserPublication
105
from lp.services.webapp.publisher import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
106
    get_current_browser_request,
107
    RedirectionView,
108
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
109
from lp.services.webapp.vhosts import allvhosts
14560.2.24 by Curtis Hovey
Moved IWebServiceApplication to lp.services.webservice.
110
from lp.services.webservice.interfaces import IWebServiceApplication
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
111
from lp.testopenid.interfaces.server import ITestOpenIDApplication
14606.4.3 by William Grant
Move the interfaces across too.
112
from lp.xmlrpc.interfaces import IPrivateApplication
7675.754.40 by Martin Pool
Add a NullFeatureController and use that in LaunchpadTestRequest
113
5435.1.11 by Leonard Richardson
Refactoring and response to feedback.
114
2649 by Canonical.com Patch Queue Manager
[trivial] small refactor of the request type used in launchpad, to make certain traversal tricks more straightforward.
115
class StepsToGo:
116
    """
117
118
    >>> class FakeRequest:
119
    ...     def __init__(self, traversed, stack):
120
    ...         self._traversed_names = traversed
121
    ...         self.stack = stack
122
    ...     def getTraversalStack(self):
123
    ...         return self.stack
124
    ...     def setTraversalStack(self, stack):
125
    ...         self.stack = stack
126
127
    >>> request = FakeRequest([], ['baz', 'bar', 'foo'])
128
    >>> stepstogo = StepsToGo(request)
129
    >>> stepstogo.startswith()
130
    True
131
    >>> stepstogo.startswith('foo')
132
    True
133
    >>> stepstogo.startswith('foo', 'bar')
134
    True
135
    >>> stepstogo.startswith('foo', 'baz')
136
    False
137
    >>> len(stepstogo)
138
    3
139
    >>> print stepstogo.consume()
140
    foo
141
    >>> request._traversed_names
142
    ['foo']
143
    >>> request.stack
144
    ['baz', 'bar']
145
    >>> print stepstogo.consume()
146
    bar
147
    >>> bool(stepstogo)
148
    True
149
    >>> print stepstogo.consume()
150
    baz
151
    >>> print stepstogo.consume()
152
    None
153
    >>> bool(stepstogo)
154
    False
155
7634.2.1 by Michael Hudson
a bundle of confusatrons and no mistake
156
    >>> request = FakeRequest([], ['baz', 'bar', 'foo'])
157
    >>> list(StepsToGo(request))
158
    ['foo', 'bar', 'baz']
159
2649 by Canonical.com Patch Queue Manager
[trivial] small refactor of the request type used in launchpad, to make certain traversal tricks more straightforward.
160
    """
161
162
    @property
163
    def _stack(self):
164
        return self.request.getTraversalStack()
165
166
    def __init__(self, request):
167
        self.request = request
168
7362.11.34 by Jonathan Lange
Make stepstogo an iterator, so that we can write more generic code in
169
    def __iter__(self):
170
        return self
171
2649 by Canonical.com Patch Queue Manager
[trivial] small refactor of the request type used in launchpad, to make certain traversal tricks more straightforward.
172
    def consume(self):
173
        """Remove the next path step and return it.
174
175
        Returns None if there are no path steps left.
176
        """
177
        stack = self.request.getTraversalStack()
178
        try:
179
            nextstep = stack.pop()
180
        except IndexError:
181
            return None
182
        self.request._traversed_names.append(nextstep)
183
        self.request.setTraversalStack(stack)
184
        return nextstep
185
7634.2.1 by Michael Hudson
a bundle of confusatrons and no mistake
186
    def next(self):
187
        value = self.consume()
188
        if value is None:
189
            raise StopIteration
190
        return value
7362.11.34 by Jonathan Lange
Make stepstogo an iterator, so that we can write more generic code in
191
2649 by Canonical.com Patch Queue Manager
[trivial] small refactor of the request type used in launchpad, to make certain traversal tricks more straightforward.
192
    def startswith(self, *args):
193
        """Return whether the steps to go start with the names given."""
194
        if not args:
195
            return True
196
        return self._stack[-len(args):] == list(reversed(args))
197
198
    def __len__(self):
199
        return len(self._stack)
200
201
    def __nonzero__(self):
202
        return bool(self._stack)
203
204
3691.3.9 by Steve Alexander
factor out Url class into webapp.url, and improve its test coverage. Add a wrapper factory for requests that calls setApplicationServer after construction, to get virtual hosting state into the request for rendering Location headers, <base> tags and so on.
205
class ApplicationServerSettingRequestFactory:
206
    """Create a request and call its setApplicationServer method.
207
208
    Due to the factory-fanatical design of this part of Zope3, we need
209
    to have a kind of proxying factory here so that we can create an
4823.6.1 by Barry Warsaw
Implementation of private XML-RPC ports. Here's the approach:
210
    appropriate request and call its setApplicationServer method before it
3691.3.9 by Steve Alexander
factor out Url class into webapp.url, and improve its test coverage. Add a wrapper factory for requests that calls setApplicationServer after construction, to get virtual hosting state into the request for rendering Location headers, <base> tags and so on.
211
    is used.
212
    """
213
214
    def __init__(self, requestfactory, host, protocol, port):
215
        self.requestfactory = requestfactory
216
        self.host = host
217
        self.protocol = protocol
218
        self.port = port
219
220
    def __call__(self, body_instream, environ, response=None):
221
        """Equivalent to the request's __init__ method."""
7575.1.1 by Francis J. Lacoste
Sets the HTTPS environment on request whose rooturl uses https so that getURL() does the right thing.
222
        # Make sure that HTTPS variable is set so that request.getURL() is
223
        # sane
224
        if self.protocol == 'https':
7575.1.2 by Francis J. Lacoste
Review feedback.
225
            environ['HTTPS'] = 'on'
3691.3.9 by Steve Alexander
factor out Url class into webapp.url, and improve its test coverage. Add a wrapper factory for requests that calls setApplicationServer after construction, to get virtual hosting state into the request for rendering Location headers, <base> tags and so on.
226
        request = self.requestfactory(body_instream, environ, response)
227
        request.setApplicationServer(self.host, self.protocol, self.port)
228
        return request
229
7014.5.13 by Maris Fogels
Added tests for allowed and denied HTTP methods.
230
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
231
class VirtualHostRequestPublicationFactory:
232
    """An `IRequestPublicationFactory` handling request to a Launchpad vhost.
233
234
    This factory will accepts requests to a particular Launchpad virtual host
235
    that matches a particular port and set of HTTP methods.
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
236
    """
2976.10.68 by Stuart Bishop
Tweak publication machinery
237
    implements(IRequestPublicationFactory)
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
238
7014.5.14 by Maris Fogels
Added the default_methods class attribute, and fixed the tests a bit.
239
    default_methods = ['GET', 'HEAD', 'POST']
240
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
241
    def __init__(self, vhost_name, request_factory, publication_factory,
242
                 port=None, methods=None, handle_default_host=False):
243
        """Creates a new factory.
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
244
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
245
        :param vhost_name: The config section defining the virtual host
246
             handled by this factory.
247
        :param request_factory: The request factory to use for this virtual
5088.2.6 by Leonard Richardson
Cleanup in response to review.
248
             host's requests.
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
249
        :param publication_factory: The publication factory to use for this
5088.2.6 by Leonard Richardson
Cleanup in response to review.
250
            virtual host's requests.
251
        :param port: The port which is handled by this factory. If
252
            this is None, this factory will handle requests that
253
            originate on any port.
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
254
        :param methods: A sequence of HTTP methods that this factory handles.
255
        :param handle_default_host: Whether or not this factory is
256
            capable of handling requests that specify no hostname.
3618.1.47 by Steve Alexander
improvements from kiko's review, and other improvements.
257
        """
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
258
259
        self.vhost_name = vhost_name
260
        self.request_factory = request_factory
261
        self.publication_factory = publication_factory
262
        self.port = port
5435.1.2 by Leonard Richardson
Added an 'api' vhost that responds to all major HTTP methods.
263
        if methods is None:
7014.5.14 by Maris Fogels
Added the default_methods class attribute, and fixed the tests a bit.
264
            methods = self.default_methods
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
265
        self.methods = methods
266
        self.handle_default_host = handle_default_host
267
268
        self.vhost_config = allvhosts.configs[self.vhost_name]
269
        self.all_hostnames = set(self.vhost_config.althostnames
270
                                 + [self.vhost_config.hostname])
3691.3.2 by Steve Alexander
set up new host header based virtual hosting, make canonical_url able to be configured with a rootsite, removal of unused IDefaultViewDirective, fix race condition in request publication factory.
271
        self._thread_local = threading.local()
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
272
        self._thread_local.environment = None
3691.3.2 by Steve Alexander
set up new host header based virtual hosting, make canonical_url able to be configured with a rootsite, removal of unused IDefaultViewDirective, fix race condition in request publication factory.
273
2976.10.68 by Stuart Bishop
Tweak publication machinery
274
    def canHandle(self, environment):
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
275
        """See `IRequestPublicationFactory`.
276
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
277
        Returns true if the HTTP host and port of the incoming request
278
        match the ones this factory is equipped to handle.
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
279
        """
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
280
        # We look at the wsgi environment to get the port this request
281
        # is coming in over.  The port number can be in one of two
282
        # places; either it's on the SERVER_PORT environment variable
283
        # or, as is the case with the test suite, it's on the
284
        # HTTP_HOST variable after a colon.
6377.1.1 by Francis J. Lacoste
SERVER_PORT takes precedence over Host-header specified port instead of raising an AssertionError.
285
        # The former takes precedence, the port from the host variable is
286
        # only checked because the test suite doesn't set SERVER_PORT.
6278.1.17 by Barry Warsaw
Handle the fact that the environment may not always have HTTP_HOST in it.
287
        host = environment.get('HTTP_HOST', '')
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
288
        port = environment.get('SERVER_PORT')
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
289
        if ":" in host:
290
            assert len(host.split(':')) == 2, (
291
                "Having a ':' in the host name isn't allowed.")
5088.2.6 by Leonard Richardson
Cleanup in response to review.
292
            host, new_port = host.split(':')
6377.1.1 by Francis J. Lacoste
SERVER_PORT takes precedence over Host-header specified port instead of raising an AssertionError.
293
            if port is None:
294
                port = new_port
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
295
296
        if host == '':
297
            if not self.handle_default_host:
298
                return False
299
        elif host not in self.all_hostnames:
300
            return False
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
301
        else:
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
302
            # This factory handles this host.
4823.6.3 by Barry Warsaw
Add a test for the private XML-RPC port functionality.
303
            pass
5088.2.6 by Leonard Richardson
Cleanup in response to review.
304
305
        if self.port is not None:
306
            if port is not None:
307
                try:
308
                    port = int(port)
309
                except (ValueError):
310
                    port = None
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
311
            if self.port != port:
312
                return False
313
314
        self._thread_local.environment = environment
5138.5.5 by Leonard Richardson
Restored old functionality where requests to the local host didn't have the application server set.
315
        self._thread_local.host = host
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
316
        return True
317
2976.10.68 by Stuart Bishop
Tweak publication machinery
318
    def __call__(self):
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
319
        """See `IRequestPublicationFactory`.
320
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
321
        We know that this factory is the right one for the given host
322
        and port. But there might be something else wrong with the
5088.2.6 by Leonard Richardson
Cleanup in response to review.
323
        request.  For instance, it might have the wrong HTTP method.
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
324
        """
325
        environment = self._thread_local.environment
326
        if environment is None:
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
327
            raise AssertionError('This factory declined the request.')
5088.2.6 by Leonard Richardson
Cleanup in response to review.
328
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
329
        root_url = URI(self.vhost_config.rooturl)
330
331
        real_request_factory, publication_factory = (
332
            self.checkRequest(environment))
333
334
        if not real_request_factory:
7014.5.23 by Francis J. Lacoste
Made /api requests on standard applications virtual host served using the webservice.
335
            real_request_factory, publication_factory = (
336
                self.getRequestAndPublicationFactories(environment))
5138.5.5 by Leonard Richardson
Restored old functionality where requests to the local host didn't have the application server set.
337
6278.1.17 by Barry Warsaw
Handle the fact that the environment may not always have HTTP_HOST in it.
338
        host = environment.get('HTTP_HOST', '').split(':')[0]
5138.5.5 by Leonard Richardson
Restored old functionality where requests to the local host didn't have the application server set.
339
        if host in ['', 'localhost']:
340
            # Sometimes requests come in to the default or local host.
341
            # If we set the application server for these requests,
342
            # they'll be handled as launchpad.net requests, and
343
            # responses will go out containing launchpad.net URLs.
344
            # That's a little unelegant, so we don't set the application
345
            # server for these requests.
346
            request_factory = real_request_factory
347
        else:
348
            request_factory = ApplicationServerSettingRequestFactory(
349
                real_request_factory,
350
                root_url.host,
351
                root_url.scheme,
352
                root_url.port)
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
353
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
354
        self._thread_local.environment = None
5088.2.6 by Leonard Richardson
Cleanup in response to review.
355
        return (request_factory, publication_factory)
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
356
7014.5.23 by Francis J. Lacoste
Made /api requests on standard applications virtual host served using the webservice.
357
    def getRequestAndPublicationFactories(self, environment):
358
        """Return the request and publication factories to use.
359
360
        You can override this method if the request and publication can
361
        vary based on the environment.
362
        """
363
        return self.request_factory, self.publication_factory
364
365
    def getAcceptableMethods(self, environment):
7014.5.26 by Francis J. Lacoste
Typos.
366
        """Return the HTTP methods acceptable in this particular environment.
7014.5.23 by Francis J. Lacoste
Made /api requests on standard applications virtual host served using the webservice.
367
        """
368
        return self.methods
369
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
370
    def checkRequest(self, environment):
371
        """Makes sure that the incoming HTTP request is of an expected type.
372
373
        This is different from canHandle() because we know the request
374
        went to the right place. It's just that it might be an invalid
375
        request for this handler.
376
377
        :return: An appropriate ProtocolErrorPublicationFactory if the
378
            HTTP request doesn't comply with the expected protocol. If
5138.5.1 by Leonard Richardson
Missing part of the initial import.
379
            the request does comply, (None, None).
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
380
        """
381
        method = environment.get('REQUEST_METHOD')
7014.5.16 by Maris Fogels
Changed and simplified the checkRequest() method override.
382
7014.5.23 by Francis J. Lacoste
Made /api requests on standard applications virtual host served using the webservice.
383
        if method in self.getAcceptableMethods(environment):
7014.5.16 by Maris Fogels
Changed and simplified the checkRequest() method override.
384
            factories = (None, None)
5088.2.6 by Leonard Richardson
Cleanup in response to review.
385
        else:
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
386
            request_factory = ProtocolErrorRequest
387
            publication_factory = ProtocolErrorPublicationFactory(
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
388
                405, headers={'Allow': " ".join(self.methods)})
7014.5.16 by Maris Fogels
Changed and simplified the checkRequest() method override.
389
            factories = (request_factory, publication_factory)
390
391
        return factories
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
392
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
393
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
394
class XMLRPCRequestPublicationFactory(VirtualHostRequestPublicationFactory):
395
    """A VirtualHostRequestPublicationFactory for XML-RPC.
396
397
    This factory only accepts XML-RPC method calls.
398
    """
399
400
    def __init__(self, vhost_name, request_factory, publication_factory,
401
                 port=None):
402
        super(XMLRPCRequestPublicationFactory, self).__init__(
403
            vhost_name, request_factory, publication_factory, port, ['POST'])
404
405
    def checkRequest(self, environment):
406
        """See `VirtualHostRequestPublicationFactory`.
407
5088.2.6 by Leonard Richardson
Cleanup in response to review.
408
        Accept only requests where the MIME type is text/xml.
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
409
        """
410
        request_factory, publication_factory = (
5088.2.6 by Leonard Richardson
Cleanup in response to review.
411
            super(XMLRPCRequestPublicationFactory, self).checkRequest(
412
                environment))
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
413
        if request_factory is None:
414
            mime_type = environment.get('CONTENT_TYPE')
5088.2.6 by Leonard Richardson
Cleanup in response to review.
415
            if mime_type != 'text/xml':
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
416
                request_factory = ProtocolErrorRequest
5088.2.6 by Leonard Richardson
Cleanup in response to review.
417
                # 415 - Unsupported Media Type
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
418
                publication_factory = ProtocolErrorPublicationFactory(415)
419
        return request_factory, publication_factory
420
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
421
5435.1.3 by Leonard Richardson
Minor changes in response to feedback.
422
class WebServiceRequestPublicationFactory(
423
    VirtualHostRequestPublicationFactory):
5435.1.2 by Leonard Richardson
Added an 'api' vhost that responds to all major HTTP methods.
424
    """A VirtualHostRequestPublicationFactory for requests against
425
    resources published through a web service.
426
    """
427
7014.5.14 by Maris Fogels
Added the default_methods class attribute, and fixed the tests a bit.
428
    default_methods = [
429
        'GET', 'HEAD', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS']
430
5435.1.2 by Leonard Richardson
Added an 'api' vhost that responds to all major HTTP methods.
431
    def __init__(self, vhost_name, request_factory, publication_factory,
432
                 port=None):
433
        """This factory accepts requests that use all five major HTTP methods.
434
        """
435
        super(WebServiceRequestPublicationFactory, self).__init__(
7014.5.14 by Maris Fogels
Added the default_methods class attribute, and fixed the tests a bit.
436
            vhost_name, request_factory, publication_factory, port)
5435.1.2 by Leonard Richardson
Added an 'api' vhost that responds to all major HTTP methods.
437
438
7014.5.13 by Maris Fogels
Added tests for allowed and denied HTTP methods.
439
class VHostWebServiceRequestPublicationFactory(
440
    VirtualHostRequestPublicationFactory):
7014.5.11 by Maris Fogels
Made the new factory understand webservice resource paths.
441
    """An `IRequestPublicationFactory` handling requests to vhosts.
442
443
    It also handles requests to the launchpad web service, if the
444
    request's path points to a web service resource.
445
    """
446
7014.5.23 by Francis J. Lacoste
Made /api requests on standard applications virtual host served using the webservice.
447
    def getAcceptableMethods(self, environment):
448
        """See `VirtualHostRequestPublicationFactory`.
449
450
        If this is a request for a webservice path, returns the appropriate
451
        methods.
452
        """
453
        if self.isWebServicePath(environment.get('PATH_INFO', '')):
454
            return WebServiceRequestPublicationFactory.default_methods
455
        else:
456
            return super(
457
                VHostWebServiceRequestPublicationFactory,
458
                self).getAcceptableMethods(environment)
459
460
    def getRequestAndPublicationFactories(self, environment):
461
        """See `VirtualHostRequestPublicationFactory`.
462
463
        If this is a request for a webservice path, returns the appropriate
464
        factories.
465
        """
466
        if self.isWebServicePath(environment.get('PATH_INFO', '')):
467
            return WebServiceClientRequest, WebServicePublication
468
        else:
469
            return super(
470
                VHostWebServiceRequestPublicationFactory,
471
                self).getRequestAndPublicationFactories(environment)
7014.5.16 by Maris Fogels
Changed and simplified the checkRequest() method override.
472
7014.5.11 by Maris Fogels
Made the new factory understand webservice resource paths.
473
    def isWebServicePath(self, path):
474
        """Does the path refer to a web service resource?"""
475
        # Add a trailing slash, if it is missing.
476
        if not path.endswith('/'):
477
            path = path + '/'
7893.3.14 by Francis J. Lacoste
Silence some lint.
478
        ws_config = getUtility(IWebServiceConfiguration)
479
        return path.startswith('/%s/' % ws_config.path_override)
7014.5.11 by Maris Fogels
Made the new factory understand webservice resource paths.
480
481
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
482
class NotFoundRequestPublicationFactory:
483
    """An IRequestPublicationFactory which always yields a 404."""
484
485
    def canHandle(self, environment):
486
        """See `IRequestPublicationFactory`."""
487
        return True
488
489
    def __call__(self):
490
        """See `IRequestPublicationFactory`.
491
492
        Unlike other publication factories, this one doesn't wrap its
493
        request factory in an ApplicationServerSettingRequestFactory.
494
        That's because it's only triggered when there's no valid hostname.
495
        """
5088.2.6 by Leonard Richardson
Cleanup in response to review.
496
        return (ProtocolErrorRequest, ProtocolErrorPublicationFactory(404))
497
498
7666.3.11 by Michael Nelson
Implemented Guilhermes' suggestions.
499
def get_query_string_params(request):
7675.284.3 by Celso Providelo
applying review comments, r=abentley.
500
    """Return a dict of the decoded query string params for a request.
501
502
    The parameter values will be decoded as unicodes, exactly as
503
    `BrowserRequest` would do to build the request form.
7666.3.11 by Michael Nelson
Implemented Guilhermes' suggestions.
504
505
    Defined here so that it can be used in both BasicLaunchpadRequest and
506
    the LaunchpadTestRequest (which doesn't inherit from
507
    BasicLaunchpadRequest).
508
    """
509
    query_string = request.get('QUERY_STRING', '')
510
7666.3.12 by Michael Nelson
Gary's further changes.
511
    # Just in case QUERY_STRING is in the environment explicitly as
7666.3.11 by Michael Nelson
Implemented Guilhermes' suggestions.
512
    # None (Some tests seem to do this, but not sure if it can ever
513
    # happen outside of tests.)
514
    if query_string is None:
515
        query_string = ''
516
7675.284.3 by Celso Providelo
applying review comments, r=abentley.
517
    parsed_qs = cgi.parse_qs(query_string, keep_blank_values=True)
518
    # Use BrowserRequest._decode() for decoding the received parameters.
519
    decoded_qs = {}
520
    for key, values in parsed_qs.iteritems():
521
        decoded_qs[key] = [
522
            request._decode(value) for value in values]
523
    return decoded_qs
524
7666.3.11 by Michael Nelson
Implemented Guilhermes' suggestions.
525
7675.834.9 by Michael Nelson
Updated is_ajax.
526
class LaunchpadBrowserRequestMixin:
7675.834.19 by Michael Nelson
Small changes from henninge's review - splitting up tests and typos.
527
    """Provides methods used for both API and web browser requests."""
7675.834.9 by Michael Nelson
Updated is_ajax.
528
529
    def getRootURL(self, rootsite):
530
        """See IBasicLaunchpadRequest."""
531
        if rootsite is not None:
532
            assert rootsite in allvhosts.configs, (
533
                "rootsite is %s.  Must be in %r." % (
534
                    rootsite, sorted(allvhosts.configs.keys())))
535
            root_url = allvhosts.configs[rootsite].rooturl
536
        else:
537
            root_url = self.getApplicationURL() + '/'
538
        return root_url
539
540
    @property
541
    def is_ajax(self):
542
        """See `IBasicLaunchpadRequest`."""
543
        return 'XMLHttpRequest' == self.getHeader('HTTP_X_REQUESTED_WITH')
544
13588.10.6 by Gavin Panella
Add include_query parameter to <request>.getURL().
545
    def getURL(self, level=0, path_only=False, include_query=False):
546
        """See `IBasicLaunchpadRequest`."""
547
        sup = super(LaunchpadBrowserRequestMixin, self)
548
        url = sup.getURL(level, path_only)
549
        if include_query:
550
            query_string = self.get('QUERY_STRING')
551
            if query_string is not None and len(query_string) > 0:
552
                url = "%s?%s" % (url, query_string)
553
        return url
554
7675.834.9 by Michael Nelson
Updated is_ajax.
555
556
class BasicLaunchpadRequest(LaunchpadBrowserRequestMixin):
14104.3.8 by Michael Hudson-Doyle
squish IParticipationExtras
557
    """Mixin request class to provide stepstogo."""
558
559
    implements(IBasicLaunchpadRequest)
2677 by Canonical.com Patch Queue Manager
[r=salgado, trivial] various improvements to navigation, and also breadcrumbs, and also redirection as a result of traversal.
560
2976.10.3 by Stuart Bishop
Launchpad is running. Test suite untried
561
    def __init__(self, body_instream, environ, response=None):
2909.1.1 by James Henstridge
Keep track of the traversed objects in the request, so they can be
562
        self.traversed_objects = []
4167.1.1 by James Henstridge
allow setInWSGIEnvironment() to change values it had previously set
563
        self._wsgi_keys = set()
5369.1.1 by Mark Shuttleworth
Add PopCalXP in date and datetime picker configurations
564
        self.needs_datepicker_iframe = False
565
        self.needs_datetimepicker_iframe = False
3258.4.4 by Steve Alexander
Made navigation components be used for XMLRPC traversal by registering them
566
        super(BasicLaunchpadRequest, self).__init__(
2976.10.3 by Stuart Bishop
Launchpad is running. Test suite untried
567
            body_instream, environ, response)
2677 by Canonical.com Patch Queue Manager
[r=salgado, trivial] various improvements to navigation, and also breadcrumbs, and also redirection as a result of traversal.
568
7703.4.1 by Francis J. Lacoste
Add Vary header to our response.
569
        # Our response always vary based on authentication.
7703.4.7 by Francis J. Lacoste
Use Authorization instead of WWW-Authenticate in Vary header.
570
        self.response.setHeader('Vary', 'Cookie, Authorization')
7703.4.1 by Francis J. Lacoste
Add Vary header to our response.
571
2649 by Canonical.com Patch Queue Manager
[trivial] small refactor of the request type used in launchpad, to make certain traversal tricks more straightforward.
572
    @property
573
    def stepstogo(self):
574
        return StepsToGo(self)
575
3485.3.54 by James Henstridge
Preserve the list of set WSGI keys over request retries, to really fix bug 92164
576
    def retry(self):
577
        """See IPublisherRequest."""
578
        new_request = super(BasicLaunchpadRequest, self).retry()
7703.4.1 by Francis J. Lacoste
Add Vary header to our response.
579
        # Propagate the list of keys we have set in the WSGI environment.
3485.3.54 by James Henstridge
Preserve the list of set WSGI keys over request retries, to really fix bug 92164
580
        new_request._wsgi_keys = self._wsgi_keys
581
        return new_request
582
2909.1.1 by James Henstridge
Keep track of the traversed objects in the request, so they can be
583
    def getNearest(self, *some_interfaces):
2909.1.10 by James Henstridge
Fix issues brought up in SteveA's review
584
        """See ILaunchpadBrowserApplicationRequest.getNearest()"""
2909.1.1 by James Henstridge
Keep track of the traversed objects in the request, so they can be
585
        for context in reversed(self.traversed_objects):
586
            for iface in some_interfaces:
587
                if iface.providedBy(context):
2909.1.10 by James Henstridge
Fix issues brought up in SteveA's review
588
                    return context, iface
589
        return None, None
2909.1.1 by James Henstridge
Keep track of the traversed objects in the request, so they can be
590
3750.1.60 by Steve Alexander
support setting wsgi environment key+value pairs from xmlrpc requests.
591
    def setInWSGIEnvironment(self, key, value):
592
        """Set a key-value pair in the WSGI environment of this request.
593
4167.1.1 by James Henstridge
allow setInWSGIEnvironment() to change values it had previously set
594
        Raises KeyError if the key is already present in the environment
595
        but not set with setInWSGIEnvironment().
3750.1.60 by Steve Alexander
support setting wsgi environment key+value pairs from xmlrpc requests.
596
        """
597
        # This method expects the BasicLaunchpadRequest mixin to be used
598
        # with a base that provides self._orig_env.
4167.1.1 by James Henstridge
allow setInWSGIEnvironment() to change values it had previously set
599
        if key not in self._wsgi_keys and key in self._orig_env:
3750.1.60 by Steve Alexander
support setting wsgi environment key+value pairs from xmlrpc requests.
600
            raise KeyError("'%s' already present in wsgi environment." % key)
601
        self._orig_env[key] = value
4167.1.1 by James Henstridge
allow setInWSGIEnvironment() to change values it had previously set
602
        self._wsgi_keys.add(key)
3750.1.60 by Steve Alexander
support setting wsgi environment key+value pairs from xmlrpc requests.
603
7666.3.8 by Michael Nelson
Moved the query_string_params dict into BasicLaunchpadRequest as the web service calls BatchNavigator.init and expects it to be in the request.
604
    @cachedproperty
605
    def query_string_params(self):
606
        """See ILaunchpadBrowserApplicationRequest."""
7666.3.11 by Michael Nelson
Implemented Guilhermes' suggestions.
607
        return get_query_string_params(self)
7666.3.8 by Michael Nelson
Moved the query_string_params dict into BasicLaunchpadRequest as the web service calls BatchNavigator.init and expects it to be in the request.
608
8310.1.14 by Guilherme Salgado
Get rid of shipit-specific code from canonical_url()
609
3258.4.4 by Steve Alexander
Made navigation components be used for XMLRPC traversal by registering them
610
class LaunchpadBrowserRequest(BasicLaunchpadRequest, BrowserRequest,
7675.834.9 by Michael Nelson
Updated is_ajax.
611
                              NotificationRequest, ErrorReportRequest):
3258.4.4 by Steve Alexander
Made navigation components be used for XMLRPC traversal by registering them
612
    """Integration of launchpad mixin request classes to make an uber
613
    launchpad request class.
614
    """
615
11132.1.3 by James Westby
Have LaunchpadBrowserRequest implement LaunchpadLayer.
616
    implements(
617
        ILaunchpadBrowserApplicationRequest, ISynchronizer,
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
618
        lp.layers.LaunchpadLayer)
3258.4.4 by Steve Alexander
Made navigation components be used for XMLRPC traversal by registering them
619
3519.1.5 by Stuart Bishop
Increase retry count to 5
620
    retry_max_count = 5    # How many times we're willing to retry
621
7055.2.15 by Michael Hudson
clear the cache on commit()
622
    def __init__(self, body_instream, environ, response=None):
623
        BasicLaunchpadRequest.__init__(self, body_instream, environ, response)
624
        transaction.manager.registerSynch(self)
625
2976.10.112 by Stuart Bishop
Fix _createReponse method signature
626
    def _createResponse(self):
3258.4.4 by Steve Alexander
Made navigation components be used for XMLRPC traversal by registering them
627
        """As per zope.publisher.browser.BrowserRequest._createResponse"""
2976.10.109 by Stuart Bishop
Merge from Rocketfuel
628
        return LaunchpadBrowserResponse()
3258.4.4 by Steve Alexander
Made navigation components be used for XMLRPC traversal by registering them
629
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
630
    @cachedproperty
631
    def form_ng(self):
632
        """See ILaunchpadBrowserApplicationRequest."""
633
        return BrowserFormNG(self.form)
634
7055.2.3 by Michael Hudson
this seems like a good idea.
635
    def setPrincipal(self, principal):
7055.2.14 by Michael Hudson
fix remaining failure
636
        self.clearSecurityPolicyCache()
637
        BrowserRequest.setPrincipal(self, principal)
638
639
    def clearSecurityPolicyCache(self):
7055.2.10 by Michael Hudson
parameterize the cache key
640
        if LAUNCHPAD_SECURITY_POLICY_CACHE_KEY in self.annotations:
641
            del self.annotations[LAUNCHPAD_SECURITY_POLICY_CACHE_KEY]
7055.2.3 by Michael Hudson
this seems like a good idea.
642
7055.2.15 by Michael Hudson
clear the cache on commit()
643
    def beforeCompletion(self, transaction):
644
        """See `ISynchronizer`."""
645
        pass
646
647
    def afterCompletion(self, transaction):
7055.2.16 by Michael Hudson
extend docstring
648
        """See `ISynchronizer`.
649
650
        We clear the cache of security policy results on commit, as objects
651
        will be refetched from the database and the security checks may result
652
        in different answers.
653
        """
7055.2.15 by Michael Hudson
clear the cache on commit()
654
        self.clearSecurityPolicyCache()
655
656
    def newTransaction(self, transaction):
657
        """See `ISynchronizer`."""
658
        pass
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
659
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
660
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
661
class BrowserFormNG:
662
    """Wrapper that provides IBrowserFormNG around a regular form dict."""
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
663
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
664
    implements(IBrowserFormNG)
665
666
    def __init__(self, form):
5318.1.2 by Edwin Grubbs
Fixed doctests
667
        """Create a new BrowserFormNG that wraps a dict containing form data.
668
        """
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
669
        self.form = form
670
671
    def __contains__(self, name):
672
        """See IBrowserFormNG."""
673
        return name in self.form
674
4173.1.2 by Francis J. Lacoste
Add __iter__ to IBrowserFormNG.
675
    def __iter__(self):
676
        """See IBrowserFormNG."""
677
        return iter(self.form)
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
678
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
679
    def getOne(self, name, default=None):
680
        """See IBrowserFormNG."""
681
        value = self.form.get(name, default)
682
        if zope_isinstance(value, (list, tuple)):
683
            raise UnexpectedFormData(
684
                'Expected only one value form field %s: %s' % (name, value))
685
        return value
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
686
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
687
    def getAll(self, name, default=None):
688
        """See IBrowserFormNG."""
689
        # We don't want a mutable as a default parameter, so we use None as a
690
        # marker.
691
        if default is None:
692
            default = []
4173.1.8 by Francis J. Lacoste
Only accept list as default for getAll
693
        else:
694
            assert zope_isinstance(default, list), (
695
                "default should be a list: %s" % default)
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
696
        value = self.form.get(name, default)
697
        if not zope_isinstance(value, list):
698
            value = [value]
699
        return value
700
3258.4.4 by Steve Alexander
Made navigation components be used for XMLRPC traversal by registering them
701
8137.8.2 by Celso Providelo
Isolating the code for converting webservice requests to webapp ones.
702
def web_service_request_to_browser_request(webservice_request):
703
    """Convert a given webservice request into a webapp one.
704
13702.1.1 by Aaron Bentley
Handle unicode URL paths gracefully in web_service_request_to_browser_request.
705
    Overrides 'SERVER_URL' to the 'mainsite', preserving headers and
706
    body.  Encodes PATH_INFO because it is unconditionally decoded by
707
    zope.publisher.http.sane_environment.
8137.8.2 by Celso Providelo
Isolating the code for converting webservice requests to webapp ones.
708
    """
709
    body = webservice_request.bodyStream.getCacheStream().read()
710
    environ = dict(webservice_request.environment)
711
    environ['SERVER_URL'] = allvhosts.configs['mainsite'].rooturl
13702.1.1 by Aaron Bentley
Handle unicode URL paths gracefully in web_service_request_to_browser_request.
712
    if 'PATH_INFO' in environ:
713
        environ['PATH_INFO'] = environ['PATH_INFO'].encode('utf-8')
8137.8.2 by Celso Providelo
Isolating the code for converting webservice requests to webapp ones.
714
    return LaunchpadBrowserRequest(body, environ)
715
716
4311.2.2 by Francis J. Lacoste
Show how the regular widgets fail. Allow uninstallation of the monkey patch.
717
class Zope3WidgetsUseIBrowserFormNGMonkeyPatch:
718
    """Make Zope3 widgets use IBrowserFormNG.
719
720
    Replace the SimpleInputWidget._getFormInput method with one using
721
    `IBrowserFormNG`.
722
    """
723
724
    installed = False
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
725
4311.2.2 by Francis J. Lacoste
Show how the regular widgets fail. Allow uninstallation of the monkey patch.
726
    @classmethod
727
    def install(cls):
728
        """Install the monkey patch."""
729
        assert not cls.installed, "Monkey patch is already installed."
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
730
4311.2.2 by Francis J. Lacoste
Show how the regular widgets fail. Allow uninstallation of the monkey patch.
731
        def _getFormInput_single(self):
732
            """Return the submitted form value.
733
734
            :raises UnexpectedFormData: If more than one value is submitted.
735
            """
736
            return self.request.form_ng.getOne(self.name)
737
738
        def _getFormInput_multi(self):
739
            """Return the submitted form values.
740
            """
741
            return self.request.form_ng.getAll(self.name)
742
743
        # Save the original method and replace it with fixed ones.
4311.2.7 by Francis J. Lacoste
Change variable name.
744
        # We don't save MultiDataHelper._getFormInput because it doesn't
745
        # override the one in SimpleInputWidget.
746
        cls._original__getFormInput = SimpleInputWidget._getFormInput
4311.2.2 by Francis J. Lacoste
Show how the regular widgets fail. Allow uninstallation of the monkey patch.
747
        SimpleInputWidget._getFormInput = _getFormInput_single
748
        MultiDataHelper._getFormInput = _getFormInput_multi
749
        cls.installed = True
4785.3.7 by Jeroen Vermeulen
Removed whitespace at ends of lines
750
4311.2.2 by Francis J. Lacoste
Show how the regular widgets fail. Allow uninstallation of the monkey patch.
751
    @classmethod
752
    def uninstall(cls):
753
        """Uninstall the monkey patch."""
754
        assert cls.installed, "Monkey patch is not installed."
755
4311.2.7 by Francis J. Lacoste
Change variable name.
756
        # Restore saved method.
757
        SimpleInputWidget._getFormInput = cls._original__getFormInput
4311.2.2 by Francis J. Lacoste
Show how the regular widgets fail. Allow uninstallation of the monkey patch.
758
        del MultiDataHelper._getFormInput
759
        cls.installed = False
760
761
762
Zope3WidgetsUseIBrowserFormNGMonkeyPatch.install()
4311.2.1 by Francis J. Lacoste
Monkey patch SimpleInputWidget._getFormValue to use IBrowserFormNG.
763
764
2728 by Canonical.com Patch Queue Manager
[r=SteveA] LaunchpadBrowserNotifications
765
class LaunchpadBrowserResponse(NotificationResponse, BrowserResponse):
766
767
    # Note that NotificationResponse defines a 'redirect' method which
768
    # needs to override the 'redirect' method in BrowserResponse
2976.10.5 by Stuart Bishop
Merge from Rocketfuel
769
    def __init__(self, header_output=None, http_transaction=None):
6061.7.2 by Curtis Hovey
This is the minimal set of changes to get make run to complete.
770
        super(LaunchpadBrowserResponse, self).__init__()
2728 by Canonical.com Patch Queue Manager
[r=SteveA] LaunchpadBrowserNotifications
771
9678.4.43 by Guilherme Salgado
[r=gary] Change LaunchpadBrowserResponse.redirect() to define trusted=True so that we don't have to change all callsites that might redirect to vhosts.
772
    def redirect(self, location, status=None, trusted=True,
9678.4.30 by Guilherme Salgado
[r=barry] Update our custom implementations of HttpResponse.redirect() to accept the new 'trusted' argument and make it default to True in LaunchpadTestResponse so that our tests can redirect anywhere they want.
773
                 temporary_if_possible=False):
3618.1.40 by Steve Alexander
add a team-restricted mode to launchpad
774
        """Do a redirect.
775
9678.4.43 by Guilherme Salgado
[r=gary] Change LaunchpadBrowserResponse.redirect() to define trusted=True so that we don't have to change all callsites that might redirect to vhosts.
776
        Unlike Zope's BrowserResponse.redirect(), consider all redirects to be
777
        trusted. Otherwise we'd have to change all callsites that redirect
778
        from lp.net to vhost.lp.net to pass trusted=True.
779
3618.1.40 by Steve Alexander
add a team-restricted mode to launchpad
780
        If temporary_if_possible is True, then do a temporary redirect
781
        if this is a HEAD or GET, otherwise do a 303.
782
783
        See RFC 2616.
3618.1.43 by Steve Alexander
add note about response.redirect() semantics and fix some trivially failing page tests.
784
785
        The interface doesn't say that redirect returns anything.
786
        However, Zope's implementation does return the location given.  This
787
        is largely useless, as it is just the location given which is often
788
        relative.  So we won't return anything.
3618.1.40 by Steve Alexander
add a team-restricted mode to launchpad
789
        """
790
        if temporary_if_possible:
791
            assert status is None, (
5318.1.2 by Edwin Grubbs
Fixed doctests
792
                "Do not set 'status' if also setting "
793
                "'temporary_if_possible'.")
3618.1.40 by Steve Alexander
add a team-restricted mode to launchpad
794
            method = self._request.method
795
            if method == 'GET' or method == 'HEAD':
796
                status = 307
797
            else:
798
                status = 303
4159.1.1 by Stuart Bishop
Fixes Bug #78780 (Handle redirects containing non-ascii characters)
799
        super(LaunchpadBrowserResponse, self).redirect(
9678.4.30 by Guilherme Salgado
[r=barry] Update our custom implementations of HttpResponse.redirect() to accept the new 'trusted' argument and make it default to True in LaunchpadTestResponse so that our tests can redirect anywhere they want.
800
            unicode(location).encode('UTF-8'), status=status, trusted=trusted)
2728 by Canonical.com Patch Queue Manager
[r=SteveA] LaunchpadBrowserNotifications
801
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
802
2728 by Canonical.com Patch Queue Manager
[r=SteveA] LaunchpadBrowserNotifications
803
def adaptResponseToSession(response):
804
    """Adapt LaunchpadBrowserResponse to ISession"""
805
    return ISession(response._request)
806
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
807
2728 by Canonical.com Patch Queue Manager
[r=SteveA] LaunchpadBrowserNotifications
808
def adaptRequestToResponse(request):
809
    """Adapt LaunchpadBrowserRequest to LaunchpadBrowserResponse"""
810
    return request.response
811
2649 by Canonical.com Patch Queue Manager
[trivial] small refactor of the request type used in launchpad, to make certain traversal tricks more straightforward.
812
13588.10.7 by Gavin Panella
Test both LaunchpadBrowserRequest and LaunchpadTestRequest.
813
class LaunchpadTestRequest(LaunchpadBrowserRequestMixin,
814
                           TestRequest, ErrorReportRequest):
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
815
    """Mock request for use in unit and functional tests.
3258.4.1 by Steve Alexander
Disable trebuchet. Make XMLRPC work, with an (untested) self-check API.
816
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
817
    >>> request = LaunchpadTestRequest(SERVER_URL='http://127.0.0.1/foo/bar')
818
819
    This class subclasses TestRequest - the standard Mock request object
820
    used in unit tests
821
822
    >>> isinstance(request, TestRequest)
823
    True
824
4108.4.10 by Guilherme Salgado
Make it possible to choose a team's renewal policy when creating/editing a team
825
    It provides LaunchpadLayer and adds a mock INotificationRequest
826
    implementation.
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
827
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
828
    >>> lp.layers.LaunchpadLayer.providedBy(request)
4108.4.10 by Guilherme Salgado
Make it possible to choose a team's renewal policy when creating/editing a team
829
    True
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
830
    >>> INotificationRequest.providedBy(request)
831
    True
832
    >>> request.uuid == request.response.uuid
833
    True
834
    >>> request.notifications is request.response.notifications
835
    True
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
836
837
    It also provides the form_ng attribute that is available from
838
    LaunchpadBrowserRequest.
839
840
    >>> from zope.interface.verify import verifyObject
841
    >>> verifyObject(IBrowserFormNG, request.form_ng)
842
    True
5369.1.12 by Mark Shuttleworth
Test fixes
843
7666.3.5 by Michael Nelson
Added query_string_params to LaunchpadBrowserRequest as suggested by Gary.
844
    It also provides the query_string_params dict that is available from
845
    LaunchpadBrowserRequest.
846
847
    >>> request = LaunchpadTestRequest(SERVER_URL='http://127.0.0.1/foo/bar',
848
    ...     QUERY_STRING='a=1&b=2&c=3')
7675.284.4 by Celso Providelo
fixing test failure.
849
    >>> request.charsets = ['utf-8']
7666.3.15 by Michael Nelson
Updated after xx-question-search-multiple-languages.txt failed.
850
    >>> request.query_string_params == {'a': ['1'], 'b': ['2'], 'c': ['3']}
7666.3.5 by Michael Nelson
Added query_string_params to LaunchpadBrowserRequest as suggested by Gary.
851
    True
852
5369.1.12 by Mark Shuttleworth
Test fixes
853
    It also provides the  hooks for popup calendar iframes:
854
855
    >>> request.needs_datetimepicker_iframe
5369.1.16 by Mark Shuttleworth
Changes based on review from JamesH
856
    False
5369.1.12 by Mark Shuttleworth
Test fixes
857
    >>> request.needs_datepicker_iframe
5369.1.16 by Mark Shuttleworth
Changes based on review from JamesH
858
    False
6520.3.2 by Guilherme Salgado
Change main-template to include javascript for json/googlemaps if the pages need them.
859
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
860
    """
14104.3.1 by Michael Hudson-Doyle
sort of manually cherry pick the introduction of IParticipationExtras
861
    implements(
862
        INotificationRequest, IBasicLaunchpadRequest, IParticipation,
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
863
        lp.layers.LaunchpadLayer)
14104.3.1 by Michael Hudson-Doyle
sort of manually cherry pick the introduction of IParticipationExtras
864
6141.3.9 by Curtis Hovey
Revisions per review.
865
    # These two attributes satisfy IParticipation.
6141.3.7 by Curtis Hovey
Removed over-engineered implementation.
866
    principal = None
867
    interaction = None
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
868
4177.4.11 by James Henstridge
add a method keyword argument to LaunchpadTestRequest
869
    def __init__(self, body_instream=None, environ=None, form=None,
870
                 skin=None, outstream=None, method='GET', **kw):
871
        super(LaunchpadTestRequest, self).__init__(
872
            body_instream=body_instream, environ=environ, form=form,
873
            skin=skin, outstream=outstream, REQUEST_METHOD=method, **kw)
4331.4.19 by James Henstridge
clean up loginservice.txt doc test, and make LaunchpadTestRequest more like a real Launchpad request object
874
        self.traversed_objects = []
5369.1.16 by Mark Shuttleworth
Changes based on review from JamesH
875
        self.needs_datepicker_iframe = False
876
        self.needs_datetimepicker_iframe = False
12809.1.2 by Brad Crittenden
Removed unnecessary comment
877
        # Use an existing feature controller if one exists, otherwise use the
878
        # null controller.
879
        self.features = get_relevant_feature_controller()
880
        if self.features is None:
881
            self.features = NullFeatureController()
4177.4.11 by James Henstridge
add a method keyword argument to LaunchpadTestRequest
882
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
883
    @property
884
    def uuid(self):
885
        return self.response.uuid
886
887
    @property
888
    def notifications(self):
4331.4.19 by James Henstridge
clean up loginservice.txt doc test, and make LaunchpadTestRequest more like a real Launchpad request object
889
        """See INotificationRequest."""
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
890
        return self.response.notifications
891
4331.4.19 by James Henstridge
clean up loginservice.txt doc test, and make LaunchpadTestRequest more like a real Launchpad request object
892
    @property
893
    def stepstogo(self):
894
        """See IBasicLaunchpadRequest."""
895
        return StepsToGo(self)
896
897
    def getNearest(self, *some_interfaces):
898
        """See IBasicLaunchpadRequest."""
899
        return None, None
900
5985.5.2 by Francis J. Lacoste
Add references leak debugging to endRequest.
901
    def setInWSGIEnvironment(self, key, value):
902
        """See IBasicLaunchpadRequest."""
903
        self._orig_env[key] = value
904
2976.10.29 by Stuart Bishop
Filter WSGI deprecation warning and update _createResponse for new Zope 3.2 design
905
    def _createResponse(self):
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
906
        """As per zope.publisher.browser.BrowserRequest._createResponse"""
2976.10.29 by Stuart Bishop
Filter WSGI deprecation warning and update _createResponse for new Zope 3.2 design
907
        return LaunchpadTestResponse()
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
908
4173.1.4 by Francis J. Lacoste
Do not use a cached property for LaunchpadTestRequest, since test something overwrite form.
909
    @property
4173.1.1 by Francis J. Lacoste
- Add IBrowserFormNG and its implementation.
910
    def form_ng(self):
911
        """See ILaunchpadBrowserApplicationRequest."""
912
        return BrowserFormNG(self.form)
913
7666.3.5 by Michael Nelson
Added query_string_params to LaunchpadBrowserRequest as suggested by Gary.
914
    @property
915
    def query_string_params(self):
916
        """See ILaunchpadBrowserApplicationRequest."""
7666.3.11 by Michael Nelson
Implemented Guilhermes' suggestions.
917
        return get_query_string_params(self)
7666.3.5 by Michael Nelson
Added query_string_params to LaunchpadBrowserRequest as suggested by Gary.
918
6141.3.12 by Curtis Hovey
Added missing setPrincipal method to LaunchpadTestRequest. Curse me for removing it to
919
    def setPrincipal(self, principal):
920
        """See `IPublicationRequest`."""
921
        self.principal = principal
922
11843.1.5 by Graham Binns
Added tests for unsubscribing and for the default value when updating.
923
    def clearSecurityPolicyCache(self):
924
        """See ILaunchpadBrowserApplicationRequest."""
925
        return
926
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
927
3112.1.22 by Brad Bollenbach
fix bug 6026 (Oops from changing bug's product when milestone is set)
928
class LaunchpadTestResponse(LaunchpadBrowserResponse):
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
929
    """Mock response for use in unit and functional tests.
930
931
    >>> request = LaunchpadTestRequest()
932
    >>> response = request.response
933
    >>> isinstance(response, LaunchpadTestResponse)
934
    True
935
    >>> INotificationResponse.providedBy(response)
936
    True
937
5594.1.10 by Maris Fogels
Fixed all of the calls to addWarningNotification() that contain HTML so that they use the structured() class.
938
    >>> response.addWarningNotification('Warning Notification')
2856.1.1 by Stuart Bishop
Add LaunchpadTestRequest and LaunchpadTestResponse for unit tests needing access to the notifications machinery
939
    >>> request.notifications[0].message
940
    u'Warning Notification'
941
    """
942
    implements(INotificationResponse)
943
944
    uuid = 'LaunchpadTestResponse'
945
946
    _notifications = None
947
948
    @property
949
    def notifications(self):
950
        if self._notifications is None:
951
            self._notifications = NotificationList()
952
        return self._notifications
953
954
2438 by Canonical.com Patch Queue Manager
r=spiv new tests for some menu system functionality. reorganisation of browser publication code.
955
class DebugLayerRequestFactory(HTTPPublicationRequestFactory):
956
    """RequestFactory that sets the DebugLayer on a request."""
957
2976.10.11 by Stuart Bishop
Resource fix and zcml tweaks. Disable trebuchet.
958
    def __call__(self, input_stream, env, output_stream=None):
2438 by Canonical.com Patch Queue Manager
r=spiv new tests for some menu system functionality. reorganisation of browser publication code.
959
        """See zope.app.publication.interfaces.IPublicationRequestFactory"""
2976.10.3 by Stuart Bishop
Launchpad is running. Test suite untried
960
        assert output_stream is None, 'output_stream is deprecated in Z3.2'
961
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
962
        # Mark the request with the 'lp.layers.debug' layer
2438 by Canonical.com Patch Queue Manager
r=spiv new tests for some menu system functionality. reorganisation of browser publication code.
963
        request = HTTPPublicationRequestFactory.__call__(
2976.10.3 by Stuart Bishop
Launchpad is running. Test suite untried
964
            self, input_stream, env)
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
965
        lp.layers.setFirstLayer(
966
            request, lp.layers.DebugLayer)
2438 by Canonical.com Patch Queue Manager
r=spiv new tests for some menu system functionality. reorganisation of browser publication code.
967
        return request
968
969
3750.1.57 by Steve Alexander
checkpoint on footprints work
970
class LaunchpadAccessLogger(CommonAccessLogger):
971
972
    def log(self, task):
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
973
        """Receives a completed task and logs it in launchpad log format.
974
975
        task IP address
4318.1.1 by Tom Haddon
Changing HTTP_X_FORWARDED_FOR to X_FORWARDED_FOR as Pound and Apache both use that variable
976
        X_FORWARDED_FOR
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
977
        HOST
978
        datetime task started
979
        request string  (1st line of request)
980
        response status
981
        response bytes written
11416.3.6 by Robert Collins
Generalise the server stats from SQL to nonpython statements.
982
        number of nonpython statements (sql, email, memcache, rabbit etc)
5521.3.2 by Guilherme Salgado
Change LaunchpadAccessLogger to log number of sqlstatements, ticks and the request duration. Also monkey patch HTTPCaller.__call__ when running pagetests to log each request.
983
        request duration
984
        number of ticks during traversal
985
        number of ticks during publication
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
986
        launchpad user id
987
        launchpad page id
988
        REFERER
989
        USER_AGENT
990
991
        """
992
        request_headers = task.request_data.headers
993
        cgi_env = task.getCGIEnvironment()
994
4318.1.1 by Tom Haddon
Changing HTTP_X_FORWARDED_FOR to X_FORWARDED_FOR as Pound and Apache both use that variable
995
        x_forwarded_for = request_headers.get('X_FORWARDED_FOR', '')
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
996
        host = request_headers.get('HOST', '')
997
        start_time = self.log_date_string(task.start_time)
998
        first_line = task.request_data.first_line
999
        status = task.status
1000
        bytes_written = task.bytes_written
1001
        userid = cgi_env.get('launchpad.userid', '')
1002
        pageid = cgi_env.get('launchpad.pageid', '')
11416.3.14 by Robert Collins
More consistently call blocking things actions.
1003
        nonpython_actions = cgi_env.get('launchpad.nonpythonactions', 0)
5521.3.2 by Guilherme Salgado
Change LaunchpadAccessLogger to log number of sqlstatements, ticks and the request duration. Also monkey patch HTTPCaller.__call__ when running pagetests to log each request.
1004
        request_duration = cgi_env.get('launchpad.requestduration', 0)
1005
        traversal_ticks = cgi_env.get('launchpad.traversalticks', 0)
1006
        publication_ticks = cgi_env.get('launchpad.publicationticks', 0)
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
1007
        referer = request_headers.get('REFERER', '')
1008
        user_agent = request_headers.get('USER_AGENT', '')
3750.1.57 by Steve Alexander
checkpoint on footprints work
1009
5585.2.8 by Brad Crittenden
Fix code formatting to adhere to coding standards.
1010
        log_template = (' - "%s" "%s" [%s] "%s" %s %d %d %s %s '
1011
                        '%s "%s" "%s" "%s" "%s"\n')
3750.1.57 by Steve Alexander
checkpoint on footprints work
1012
        self.output.logRequest(
1013
            task.channel.addr[0],
5585.2.8 by Brad Crittenden
Fix code formatting to adhere to coding standards.
1014
            log_template % (
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
1015
                x_forwarded_for,
1016
                host,
1017
                start_time,
1018
                first_line,
1019
                status,
1020
                bytes_written,
11416.3.14 by Robert Collins
More consistently call blocking things actions.
1021
                nonpython_actions,
5521.3.2 by Guilherme Salgado
Change LaunchpadAccessLogger to log number of sqlstatements, ticks and the request duration. Also monkey patch HTTPCaller.__call__ when running pagetests to log each request.
1022
                request_duration,
1023
                traversal_ticks,
1024
                publication_ticks,
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
1025
                userid,
1026
                pageid,
3750.1.57 by Steve Alexander
checkpoint on footprints work
1027
                referer,
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
1028
                user_agent,
3750.1.57 by Steve Alexander
checkpoint on footprints work
1029
                )
3750.1.59 by Steve Alexander
launchpad request logging, including page id, launchpad user id and remote ip address
1030
           )
3750.1.57 by Steve Alexander
checkpoint on footprints work
1031
1032
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
1033
http = wsgi.ServerType(
13125.1.2 by Brad Crittenden
Fixed lint
1034
    ZServerTracelogServer,  # subclass of WSGIHTTPServer
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
1035
    WSGIPublisherApplication,
3750.1.57 by Steve Alexander
checkpoint on footprints work
1036
    LaunchpadAccessLogger,
2438 by Canonical.com Patch Queue Manager
r=spiv new tests for some menu system functionality. reorganisation of browser publication code.
1037
    8080,
1038
    True)
1039
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
1040
pmhttp = wsgi.ServerType(
1041
    PMDBWSGIHTTPServer,
1042
    WSGIPublisherApplication,
3750.1.57 by Steve Alexander
checkpoint on footprints work
1043
    LaunchpadAccessLogger,
2438 by Canonical.com Patch Queue Manager
r=spiv new tests for some menu system functionality. reorganisation of browser publication code.
1044
    8081,
1045
    True)
1046
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
1047
debughttp = wsgi.ServerType(
13125.1.2 by Brad Crittenden
Fixed lint
1048
    ZServerTracelogServer,  # subclass of WSGIHTTPServer
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
1049
    WSGIPublisherApplication,
3750.1.57 by Steve Alexander
checkpoint on footprints work
1050
    LaunchpadAccessLogger,
2438 by Canonical.com Patch Queue Manager
r=spiv new tests for some menu system functionality. reorganisation of browser publication code.
1051
    8082,
3673.4.1 by Bjorn Tillenius
switch to use WSGI servers instead of the old ones. initial go at choosing request,publication by looking at the Host header.
1052
    True,
1053
    requestFactory=DebugLayerRequestFactory)
3618.1.49 by Steve Alexander
various small refactorings, change features.launchpad.dev to blueprint.launchpad.dev, implement code.launchpad.dev
1054
4823.6.1 by Barry Warsaw
Implementation of private XML-RPC ports. Here's the approach:
1055
privatexmlrpc = wsgi.ServerType(
13125.1.2 by Brad Crittenden
Fixed lint
1056
    ZServerTracelogServer,  # subclass of WSGIHTTPServer
4823.6.1 by Barry Warsaw
Implementation of private XML-RPC ports. Here's the approach:
1057
    WSGIPublisherApplication,
1058
    LaunchpadAccessLogger,
1059
    8080,
1060
    True)
3618.1.49 by Steve Alexander
various small refactorings, change features.launchpad.dev to blueprint.launchpad.dev, implement code.launchpad.dev
1061
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
1062
3618.1.49 by Steve Alexander
various small refactorings, change features.launchpad.dev to blueprint.launchpad.dev, implement code.launchpad.dev
1063
# ---- mainsite
1064
1065
class MainLaunchpadPublication(LaunchpadBrowserPublication):
1066
    """The publication used for the main Launchpad site."""
1067
7703.4.3 by Francis J. Lacoste
Use proper Vary for answers and web service.
1068
7675.117.11 by Francis J. Lacoste
Extracted AccountPrincipalMixin so that ShipItPublication doesn't inherit from IdPublication
1069
class AccountPrincipalMixin:
1070
    """Mixin for publication that works with person-less accounts."""
7675.85.2 by Jonathan Lange
Undo revision generated by step 2 of process.
1071
1072
    def getPrincipal(self, request):
1073
        """Return the authenticated principal for this request.
1074
1075
        This is only necessary because, unlike in LaunchpadBrowserPublication,
1076
        here we want principals representing personless accounts to be
1077
        returned, so that personless accounts can use our OpenID server.
1078
        """
1079
        auth_utility = getUtility(IPlacelessAuthUtility)
1080
        principal = auth_utility.authenticate(request)
1081
        if principal is None:
1082
            principal = auth_utility.unauthenticatedPrincipal()
1083
            assert principal is not None, "Missing unauthenticated principal."
1084
        return principal
1085
1086
4934.3.2 by Elliot Murphy
checkpoint after adding FeedsLayer
1087
# ---- feeds
4823.6.1 by Barry Warsaw
Implementation of private XML-RPC ports. Here's the approach:
1088
4934.3.9 by Elliot Murphy
Renaming from Feed to Feeds.
1089
class FeedsPublication(LaunchpadBrowserPublication):
4934.3.1 by Elliot Murphy
checkpoint
1090
    """The publication used for Launchpad feed requests."""
1091
4934.3.9 by Elliot Murphy
Renaming from Feed to Feeds.
1092
    root_object_interface = IFeedsApplication
5318.1.2 by Edwin Grubbs
Fixed doctests
1093
5318.1.1 by Edwin Grubbs
Restricted feed.lp.net to serving feeds
1094
    def traverseName(self, request, ob, name):
5318.1.2 by Edwin Grubbs
Fixed doctests
1095
        """Override traverseName to restrict urls on feeds.launchpad.net.
1096
1097
        Feeds.lp.net should only serve classes that implement the IFeed
1098
        interface or redirect to some other url.
1099
        """
6768.2.4 by Edwin Grubbs
Allow images to be served from feeds.lp.net subdomain
1100
        # LaunchpadImageFolder is imported here to avoid an import loop.
13130.1.1 by Curtis Hovey
Moved launchpad.py to lp.app.browser
1101
        from lp.app.browser.launchpad import LaunchpadImageFolder
5318.1.1 by Edwin Grubbs
Restricted feed.lp.net to serving feeds
1102
        result = super(FeedsPublication, self).traverseName(request, ob, name)
1103
        if len(request.stepstogo) == 0:
5318.1.2 by Edwin Grubbs
Fixed doctests
1104
            # The url has been fully traversed. Now we can check that
6768.2.4 by Edwin Grubbs
Allow images to be served from feeds.lp.net subdomain
1105
            # the result is a feed, an image, or a redirection.
5318.1.1 by Edwin Grubbs
Restricted feed.lp.net to serving feeds
1106
            naked_result = removeSecurityProxy(result)
5318.1.2 by Edwin Grubbs
Fixed doctests
1107
            if (IFeed.providedBy(result) or
6768.2.4 by Edwin Grubbs
Allow images to be served from feeds.lp.net subdomain
1108
                isinstance(naked_result, LaunchpadImageFolder) or
5318.1.2 by Edwin Grubbs
Fixed doctests
1109
                getattr(naked_result, 'status', None) == 301):
5318.1.1 by Edwin Grubbs
Restricted feed.lp.net to serving feeds
1110
                return result
1111
            else:
5494.2.1 by Edwin Grubbs
Fixed bug 177949. http://feeds.launchpad.net/bugs should be NotFound instead of an oops
1112
                raise NotFound(self, '', request)
5318.1.1 by Edwin Grubbs
Restricted feed.lp.net to serving feeds
1113
        else:
5318.1.2 by Edwin Grubbs
Fixed doctests
1114
            # There are still url segments to traverse.
5318.1.1 by Edwin Grubbs
Restricted feed.lp.net to serving feeds
1115
            return result
4934.3.9 by Elliot Murphy
Renaming from Feed to Feeds.
1116
5585.2.2 by Brad Crittenden
Extracted getPrincipal method from LaunchpadBrowserPublication.
1117
    def getPrincipal(self, request):
1118
        """For feeds always return the anonymous user."""
1119
        auth_utility = getUtility(IPlacelessAuthUtility)
1120
        return auth_utility.unauthenticatedPrincipal()
1121
4934.3.9 by Elliot Murphy
Renaming from Feed to Feeds.
1122
1123
class FeedsBrowserRequest(LaunchpadBrowserRequest):
4934.3.1 by Elliot Murphy
checkpoint
1124
    """Request type for a launchpad feed."""
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
1125
    implements(lp.layers.FeedsLayer)
4934.3.1 by Elliot Murphy
checkpoint
1126
10065.2.3 by Guilherme Salgado
Move the test openid views under a newly created testopenid vhost.
1127
11073.1.6 by Guilherme Salgado
Fix the IAbsoluteURL issue
1128
# ---- apidoc
1129
1130
class APIDocBrowserRequest(LaunchpadBrowserRequest):
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
1131
    implements(lp.layers.APIDocLayer)
11073.1.6 by Guilherme Salgado
Fix the IAbsoluteURL issue
1132
1133
1134
class APIDocBrowserPublication(LaunchpadBrowserPublication):
1135
    root_object_interface = IAPIDocRoot
1136
1137
10065.2.3 by Guilherme Salgado
Move the test openid views under a newly created testopenid vhost.
1138
# ---- testopenid
1139
1140
class TestOpenIDBrowserRequest(LaunchpadBrowserRequest):
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
1141
    implements(lp.layers.TestOpenIDLayer)
10065.2.3 by Guilherme Salgado
Move the test openid views under a newly created testopenid vhost.
1142
1143
1144
class TestOpenIDBrowserPublication(LaunchpadBrowserPublication):
1145
    root_object_interface = ITestOpenIDApplication
1146
1147
5435.1.2 by Leonard Richardson
Added an 'api' vhost that responds to all major HTTP methods.
1148
# ---- web service
1149
7893.3.4 by Francis J. Lacoste
Moved generic webservice publisher stuff to canonical.lazr.rest.publisher.
1150
class WebServicePublication(WebServicePublicationMixin,
1151
                            LaunchpadBrowserPublication):
5435.1.6 by Leonard Richardson
Implement a 'hello world' web service.
1152
    """The publication used for Launchpad web service requests."""
1153
1154
    root_object_interface = IWebServiceApplication
1155
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
1156
    def constructPageID(self, view, context):
11351.1.4 by Diogo Matsubara
review comments: change test to be more clear about the pageid for CollectionResource, add comment explaining where the additional page id info comes from and a pointer to wiki page explaining WebService page ids.
1157
        """Add the web service named operation (if any) to the page ID.
1158
1159
        See https://dev.launchpad.net/Foundations/Webservice for more
1160
        information about WebService page IDs.
1161
        """
11178.1.2 by Benji York
implement Gary's suggested improvments
1162
        pageid = super(WebServicePublication, self).constructPageID(
1163
            view, context)
11351.1.1 by Diogo Matsubara
Fix bug 606184 (API Pageid for collections is 'scopedcollection:collectionresource' which does not mention the origin page id)
1164
        if ICollectionResource.providedBy(view):
11351.1.4 by Diogo Matsubara
review comments: change test to be more clear about the pageid for CollectionResource, add comment explaining where the additional page id info comes from and a pointer to wiki page explaining WebService page ids.
1165
            # collection_identifier is a way to differentiate between
1166
            # CollectionResource objects. CollectionResource objects are
1167
            # objects that serve a list of Entry resources through the
1168
            # WebService, so by querying the CollectionResource.type_url
1169
            # attribute we're able to find out the resource type the
1170
            # collection holds. See lazr.restful._resource.py to see how
1171
            # the type_url is constructed.
1172
            # We don't need the full URL, just the type of the resource.
11351.1.1 by Diogo Matsubara
Fix bug 606184 (API Pageid for collections is 'scopedcollection:collectionresource' which does not mention the origin page id)
1173
            collection_identifier = view.type_url.split('/')[-1]
1174
            if collection_identifier:
1175
                pageid += ':' + collection_identifier
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
1176
        op = (view.request.get('ws.op')
1177
            or view.request.query_string_params.get('ws.op'))
13840.1.1 by Benji York
fix bug
1178
        if op and isinstance(op, basestring):
11178.1.2 by Benji York
implement Gary's suggested improvments
1179
            pageid += ':' + op
1180
        return pageid
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
1181
5863.16.3 by Leonard Richardson
Objects served through the web service should always be served with the WebServiceApplication, no matter what application is used to serve them through the web site.
1182
    def getApplication(self, request):
1183
        """See `zope.publisher.interfaces.IPublication`.
1184
1185
        Always use the web service application to serve web service
1186
        resources, no matter what application is normally used to serve
1187
        the underlying objects.
1188
        """
1189
        return getUtility(IWebServiceApplication)
1190
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1191
    def getResource(self, request, ob):
5835.6.21 by Francis J. Lacoste
Updated grammar and style based on sinzui's comments.
1192
        """Return the resource that can publish the object ob.
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1193
1194
        This is done at the end of traversal.  If the published object
1195
        supports the ICollection, or IEntry interface we wrap it into the
1196
        appropriate resource.
1197
        """
7893.3.4 by Francis J. Lacoste
Moved generic webservice publisher stuff to canonical.lazr.rest.publisher.
1198
        if zope_isinstance(ob, RedirectionView):
6793.3.1 by Leonard Richardson
Renamed class, fixed test.
1199
            # A redirection should be served as is.
1200
            return ob
5835.6.14 by Francis J. Lacoste
Only allow publishing of IHTTPResource or objects that have an IEntry or ICollection adapters.
1201
        else:
7893.3.4 by Francis J. Lacoste
Moved generic webservice publisher stuff to canonical.lazr.rest.publisher.
1202
            return super(WebServicePublication, self).getResource(request, ob)
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1203
14080.3.1 by Gavin Panella
Issue an FinishReadOnlyRequestEvent at the end of a read-only request.
1204
    def finishReadOnlyRequest(self, request, ob, txn):
5796.17.1 by Guilherme Salgado
Do not abort the transaction in GET requests on api.launchpad.dev in order to make sure newly created nonces are actually stored.
1205
        """Commit the transaction so that created OAuthNonces are stored."""
14080.3.9 by Gavin Panella
Demonstrate the FinishReadOnlyRequestEvent is fired by both LaunchpadBrowserPublication and WebServicePublication.
1206
        notify(FinishReadOnlyRequestEvent(ob, request))
6061.2.57 by Gary Poster
Add handling of doomed transactions to our custom publisher.
1207
        # Transaction commits usually need to be aware of the possibility of
1208
        # a doomed transaction.  We do not expect that this code will
1209
        # encounter doomed transactions.  If it does, this will need to be
1210
        # revisited.
5796.17.1 by Guilherme Salgado
Do not abort the transaction in GET requests on api.launchpad.dev in order to make sure newly created nonces are actually stored.
1211
        txn.commit()
1212
5796.16.2 by Guilherme Salgado
Change WebServicePublication.getPrincipal() to return the person associated with the access token in the request, if everything is okay in the request and the signature matches.
1213
    def getPrincipal(self, request):
7014.5.24 by Francis J. Lacoste
Use regular authentication on non-API host.
1214
        """See `LaunchpadBrowserPublication`.
1215
7014.5.26 by Francis J. Lacoste
Typos.
1216
        Web service requests are authenticated using OAuth, except for the
7014.5.24 by Francis J. Lacoste
Use regular authentication on non-API host.
1217
        one made using (presumably) JavaScript on the /api override path.
13125.1.1 by Brad Crittenden
Prevent API token errors from causing an OOPS
1218
1219
        Raises a variety of token errors (ClockSkew, NonceAlreadyUsed,
1220
        TimestampOrderingError, TokenException) which have a webservice error
1221
        status of Unauthorized - 401.  All of these exceptions represent
1222
        errors on the part of the client.
1223
1224
        Raises Unauthorized directly in the case where the consumer is None
1225
        for a non-anonymous request as it may represent a server error.
7014.5.24 by Francis J. Lacoste
Use regular authentication on non-API host.
1226
        """
1227
        # Use the regular HTTP authentication, when the request is not
1228
        # on the API virtual host but comes through the path_override on
1229
        # the other regular virtual hosts.
1230
        request_path = request.get('PATH_INFO', '')
7895.1.10 by Leonard Richardson
Fixed test failures.
1231
        web_service_config = getUtility(IWebServiceConfiguration)
1232
        if request_path.startswith("/%s" % web_service_config.path_override):
7014.5.24 by Francis J. Lacoste
Use regular authentication on non-API host.
1233
            return super(WebServicePublication, self).getPrincipal(request)
1234
11579.1.1 by Leonard Richardson
Reverted my earlier change.
1235
        # Fetch OAuth authorization information from the request.
1236
        form = get_oauth_authorization(request)
1237
1238
        consumer_key = form.get('oauth_consumer_key')
1239
        consumers = getUtility(IOAuthConsumerSet)
1240
        consumer = consumers.getByKey(consumer_key)
1241
        token_key = form.get('oauth_token')
1242
        anonymous_request = (token_key == '')
1243
1244
        if consumer_key is None:
1245
            # Either the client's OAuth implementation is broken, or
1246
            # the user is trying to make an unauthenticated request
1247
            # using wget or another OAuth-ignorant application.
1248
            # Try to retrieve a consumer based on the User-Agent
1249
            # header.
1250
            anonymous_request = True
13085.3.10 by Deryck Hodge
need to preserve getting the consumer key from user agent after all.
1251
            consumer_key = request.getHeader('User-Agent', '')
1252
            if consumer_key == '':
1253
                consumer_key = 'anonymous client'
11579.1.1 by Leonard Richardson
Reverted my earlier change.
1254
            consumer = consumers.getByKey(consumer_key)
1255
1256
        if consumer is None:
1257
            if anonymous_request:
1258
                # This is the first time anyone has tried to make an
1259
                # anonymous request using this consumer name (or user
1260
                # agent). Dynamically create the consumer.
1261
                #
1262
                # In the normal website this wouldn't be possible
1263
                # because GET requests have their transactions rolled
1264
                # back. But webservice requests always have their
1265
                # transactions committed so that we can keep track of
1266
                # the OAuth nonces and prevent replay attacks.
1267
                if consumer_key == '' or consumer_key is None:
13125.1.1 by Brad Crittenden
Prevent API token errors from causing an OOPS
1268
                    raise TokenException("No consumer key specified.")
11579.1.1 by Leonard Richardson
Reverted my earlier change.
1269
                consumer = consumers.new(consumer_key, '')
1270
            else:
1271
                # An unknown consumer can never make a non-anonymous
1272
                # request, because access tokens are registered with a
1273
                # specific, known consumer.
1274
                raise Unauthorized('Unknown consumer (%s).' % consumer_key)
1275
        if anonymous_request:
1276
            # Skip the OAuth verification step and let the user access the
1277
            # web service as an unauthenticated user.
1278
            #
1279
            # XXX leonardr 2009-12-15 bug=496964: Ideally we'd be
1280
            # auto-creating a token for the anonymous user the first
1281
            # time, passing it through the OAuth verification step,
1282
            # and using it on all subsequent anonymous requests.
1283
            alsoProvides(request, IOAuthSignedRequest)
1284
            auth_utility = getUtility(IPlacelessAuthUtility)
1285
            return auth_utility.unauthenticatedPrincipal()
1286
        token = consumer.getAccessToken(token_key)
1287
        if token is None:
13125.1.1 by Brad Crittenden
Prevent API token errors from causing an OOPS
1288
            raise TokenException('Unknown access token (%s).' % token_key)
11579.1.1 by Leonard Richardson
Reverted my earlier change.
1289
        nonce = form.get('oauth_nonce')
1290
        timestamp = form.get('oauth_timestamp')
13125.1.1 by Brad Crittenden
Prevent API token errors from causing an OOPS
1291
        token.checkNonceAndTimestamp(nonce, timestamp)
11579.1.1 by Leonard Richardson
Reverted my earlier change.
1292
        if token.permission == OAuthPermission.UNAUTHORIZED:
13125.1.1 by Brad Crittenden
Prevent API token errors from causing an OOPS
1293
            raise TokenException('Unauthorized token (%s).' % token.key)
11694.2.1 by Leonard Richardson
Initial implementation.
1294
        elif token.is_expired:
13125.1.1 by Brad Crittenden
Prevent API token errors from causing an OOPS
1295
            raise TokenException('Expired token (%s).' % token.key)
11579.1.1 by Leonard Richardson
Reverted my earlier change.
1296
        elif not check_oauth_signature(request, consumer, token):
13125.1.1 by Brad Crittenden
Prevent API token errors from causing an OOPS
1297
            raise TokenException('Invalid signature.')
11579.1.1 by Leonard Richardson
Reverted my earlier change.
1298
        else:
1299
            # Everything is fine, let's return the principal.
1300
            pass
1301
        alsoProvides(request, IOAuthSignedRequest)
1302
        principal = getUtility(IPlacelessLoginSource).getPrincipal(
1303
            token.person.account.id, access_level=token.permission,
1304
            scope=token.context)
1305
1306
        return principal
6314.7.1 by Leonard Richardson
Initial implementation.
1307
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1308
11936.1.3 by Abel Deuring
revert patch for LaunchpadWebServiceRequestTraversal. We need the method getRootURL() of mixin class WebServiceClientRequest not only in WebServiceClientRequest, but also in WebServiceTestRequest
1309
class LaunchpadWebServiceRequestTraversal(WebServiceRequestTraversal):
14600.1.8 by Curtis Hovey
Move c.l.layers to lp.
1310
    implements(lp.layers.WebServiceLayer)
11936.1.3 by Abel Deuring
revert patch for LaunchpadWebServiceRequestTraversal. We need the method getRootURL() of mixin class WebServiceClientRequest not only in WebServiceClientRequest, but also in WebServiceTestRequest
1311
1312
    def getRootURL(self, rootsite):
1313
        """See IBasicLaunchpadRequest."""
1314
        # When browsing the web service, we want URLs to point back at the web
1315
        # service, so we basically ignore rootsite.
1316
        return self.getApplicationURL() + '/'
1317
1318
1319
class WebServiceClientRequest(LaunchpadWebServiceRequestTraversal,
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1320
                              LaunchpadBrowserRequest):
5435.1.2 by Leonard Richardson
Added an 'api' vhost that responds to all major HTTP methods.
1321
    """Request type for a resource published through the web service."""
1322
7703.4.3 by Francis J. Lacoste
Use proper Vary for answers and web service.
1323
    def __init__(self, body_instream, environ, response=None):
1324
        super(WebServiceClientRequest, self).__init__(
1325
            body_instream, environ, response)
10790.2.1 by Leonard Richardson
Merged already-reviewed diff into a new branch to avoid having to resolve lots of conflicts.
1326
        # Web service requests use content negotiation, so we put
1327
        # 'Accept' in the Vary header. They don't use cookies, so
1328
        # there's no point in putting 'Cookie' in the Vary header, and
1329
        # putting 'Authorization' in the Vary header totally destroys
1330
        # caching because every web service request contains a
1331
        # distinct OAuth nonce in its Authorization header.
1332
        #
1333
        # Because 'Authorization' is not in the Vary header, a client
1334
        # that reuses a single cache for different OAuth credentials
1335
        # could conceivably leak private information to an
1336
        # unprivileged user via the cache. This won't happen for the
1337
        # web service root resource because the service root is the
1338
        # same for everybody. It won't happen for entry resources
1339
        # because if two users have a different representation of an
1340
        # entry, the ETag will also be different and a conditional
1341
        # request will fail.
1342
        #
1343
        # Once lazr.restful starts setting caching directives other
1344
        # than ETag, we may have to revisit this.
1345
        self.response.setHeader('Vary', 'Accept')
7703.4.3 by Francis J. Lacoste
Use proper Vary for answers and web service.
1346
11936.1.3 by Abel Deuring
revert patch for LaunchpadWebServiceRequestTraversal. We need the method getRootURL() of mixin class WebServiceClientRequest not only in WebServiceClientRequest, but also in WebServiceTestRequest
1347
1348
class WebServiceTestRequest(LaunchpadWebServiceRequestTraversal,
1349
                            LaunchpadTestRequest):
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1350
    """Test request for the webservice.
1351
1352
    It provides the WebServiceLayer and supports the getResource()
1353
    web publication hook.
1354
    """
1355
10304.5.3 by Leonard Richardson
Mark test requests with a version-specific marker interface.
1356
    def __init__(self, body_instream=None, environ=None, version=None, **kw):
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1357
        test_environ = {
10420.4.7 by Leonard Richardson
Code cleanup.
1358
            'SERVER_URL': 'http://api.launchpad.dev',
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1359
            'HTTP_HOST': 'api.launchpad.dev',
1360
            }
1361
        if environ is not None:
1362
            test_environ.update(environ)
1363
        super(WebServiceTestRequest, self).__init__(
1364
            body_instream=body_instream, environ=test_environ, **kw)
10304.5.3 by Leonard Richardson
Mark test requests with a version-specific marker interface.
1365
        if version is None:
1366
            version = getUtility(IWebServiceConfiguration).active_versions[-1]
1367
        self.version = version
1368
        version_marker = getUtility(IWebServiceVersion, name=version)
1369
        alsoProvides(self, version_marker)
5835.6.7 by Francis J. Lacoste
Remove code related to having our resources provide IPublishTraverse and ICanonicalUrlData. Regular Nanvigation and browser:url will be used to setup traversal/canonical_url.
1370
1371
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
1372
# ---- xmlrpc
1373
1374
class PublicXMLRPCPublication(LaunchpadBrowserPublication):
1375
    """The publication used for public XML-RPC requests."""
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
1376
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
1377
    def handleException(self, object, request, exc_info, retry_allowed=True):
1378
        LaunchpadBrowserPublication.handleException(
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
1379
                self, object, request, exc_info, retry_allowed)
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
1380
        OpStats.stats['xml-rpc faults'] += 1
1381
1382
    def endRequest(self, request, object):
1383
        OpStats.stats['xml-rpc requests'] += 1
1384
        return LaunchpadBrowserPublication.endRequest(self, request, object)
1385
1386
1387
class PublicXMLRPCRequest(BasicLaunchpadRequest, XMLRPCRequest,
7675.834.9 by Michael Nelson
Updated is_ajax.
1388
                          ErrorReportRequest):
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
1389
    """Request type for doing public XML-RPC in Launchpad."""
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1390
1391
    def _createResponse(self):
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
1392
        return PublicXMLRPCResponse()
1393
1394
1395
class PublicXMLRPCResponse(XMLRPCResponse):
1396
    """Response type for doing public XML-RPC in Launchpad."""
1397
1398
    def handleException(self, exc_info):
1399
        # If we don't have a proper xmlrpclib.Fault, and we have
1400
        # logged an OOPS, create a Fault that reports the OOPS ID to
1401
        # the user.
1402
        exc_value = exc_info[1]
1403
        if not isinstance(exc_value, xmlrpclib.Fault):
1404
            request = get_current_browser_request()
1405
            if request is not None and request.oopsid is not None:
1406
                exc_info = (xmlrpclib.Fault,
1407
                            xmlrpclib.Fault(-1, request.oopsid),
1408
                            None)
1409
        XMLRPCResponse.handleException(self, exc_info)
1410
1411
1412
class PrivateXMLRPCPublication(PublicXMLRPCPublication):
1413
    """The publication used for private XML-RPC requests."""
1414
1415
    root_object_interface = IPrivateApplication
1416
1417
    def traverseName(self, request, ob, name):
1418
        """Traverse to an end point or let normal traversal do its thing."""
1419
        assert isinstance(request, PrivateXMLRPCRequest), (
1420
            'Not a private XML-RPC request')
1421
        missing = object()
1422
        end_point = getattr(ob, name, missing)
1423
        if end_point is missing:
1424
            return super(PrivateXMLRPCPublication, self).traverseName(
1425
                request, ob, name)
1426
        return end_point
1427
1428
1429
class PrivateXMLRPCRequest(PublicXMLRPCRequest):
1430
    """Request type for doing private XML-RPC in Launchpad."""
1431
    # For now, the same as public requests.
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1432
9601.1.1 by Barry Warsaw
Fix the OOPSes in bug 403606 by passing holdMessage() data wrapped in an
1433
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1434
# ---- Protocol errors
1435
1436
class ProtocolErrorRequest(LaunchpadBrowserRequest):
1437
    """An HTTP request that happened to result in an HTTP error."""
1438
11073.1.7 by Guilherme Salgado
Fix a bunch of remaining minor issues.
1439
    def traverse(self, object):
1440
        """It's already been determined that there's an error. Return None."""
1441
        return None
1442
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1443
1444
class ProtocolErrorPublicationFactory:
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1445
    """This class publishes error messages in response to protocol errors."""
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1446
1447
    def __init__(self, status, headers=None):
5088.2.6 by Leonard Richardson
Cleanup in response to review.
1448
        """Store the headers and status for turning into a parameterized
1449
        publication.
1450
        """
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1451
        if not headers:
1452
            headers = {}
1453
        self.status = status
1454
        self.headers = headers
1455
1456
    def __call__(self, db):
5088.2.6 by Leonard Richardson
Cleanup in response to review.
1457
        """Create a parameterized publication object."""
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1458
        return ProtocolErrorPublication(self.status, self.headers)
1459
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1460
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1461
class ProtocolErrorPublication(LaunchpadBrowserPublication):
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1462
    """Publication used for requests that turn out to be protocol errors."""
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1463
1464
    def __init__(self, status, headers):
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1465
        """Prepare to construct a ProtocolErrorException
1466
1467
        :param status: The HTTP status to send
1468
        :param headers: Any HTTP headers that should be sent.
1469
        """
5318.1.6 by Edwin Grubbs
Fixed call to LaunchpadBrowserPublication.__init__
1470
        super(ProtocolErrorPublication, self).__init__(None)
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1471
        self.status = status
1472
        self.headers = headers
1473
1474
    def callObject(self, request, object):
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1475
        """Raise an approprate exception for this protocol error."""
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1476
        if self.status == 404:
5206.2.1 by Leonard Richardson
Added test and fix.
1477
            raise NotFound(self, '', request)
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1478
        else:
1479
            raise ProtocolErrorException(self.status, self.headers)
1480
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1481
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1482
class ProtocolErrorException(Exception):
5318.1.2 by Edwin Grubbs
Fixed doctests
1483
    """An exception for requests that turn out to be protocol errors."""
5088.2.3 by Leonard Richardson
Added a view for protocol errors so they'll be published as objects instead of being treated as catastrophes.
1484
    implements(ILaunchpadProtocolError)
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1485
1486
    def __init__(self, status, headers):
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1487
        """Store status and headers for rendering in the HTTP response."""
5318.1.2 by Edwin Grubbs
Fixed doctests
1488
        Exception.__init__(self)
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1489
        self.status = status
1490
        self.headers = headers
1491
1492
    def __str__(self):
5318.1.2 by Edwin Grubbs
Fixed doctests
1493
        """A protocol error can be well-represented by its HTTP status code.
1494
        """
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1495
        return "Protocol error: %s" % self.status
1496
6279.1.8 by Francis J. Lacoste
Use time before hard timeout expires as Launchpad default time out value.
1497
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1498
def register_launchpad_request_publication_factories():
1499
    """Register our factories with the Zope3 publisher.
1500
1501
    DEATH TO ZCML!
1502
    """
1503
    VHRP = VirtualHostRequestPublicationFactory
7014.5.23 by Francis J. Lacoste
Made /api requests on standard applications virtual host served using the webservice.
1504
    VWSHRP = VHostWebServiceRequestPublicationFactory
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1505
1506
    factories = [
7014.5.23 by Francis J. Lacoste
Made /api requests on standard applications virtual host served using the webservice.
1507
        VWSHRP('mainsite', LaunchpadBrowserRequest, MainLaunchpadPublication,
1508
               handle_default_host=True),
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
1509
        VHRP('feeds', FeedsBrowserRequest, FeedsPublication),
7893.3.4 by Francis J. Lacoste
Moved generic webservice publisher stuff to canonical.lazr.rest.publisher.
1510
        WebServiceRequestPublicationFactory(
1511
            'api', WebServiceClientRequest, WebServicePublication),
1512
        XMLRPCRequestPublicationFactory(
11178.1.1 by Benji York
add code/tests for generating the page ID differently for web service requests
1513
            'xmlrpc', PublicXMLRPCRequest, PublicXMLRPCPublication),
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1514
        ]
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1515
10212.5.8 by Guilherme Salgado
New config to decide whether or not to enable the test openid provider
1516
    if config.launchpad.enable_test_openid_provider:
10065.2.3 by Guilherme Salgado
Move the test openid views under a newly created testopenid vhost.
1517
        factories.append(VHRP('testopenid', TestOpenIDBrowserRequest,
1518
                              TestOpenIDBrowserPublication))
1519
11073.1.6 by Guilherme Salgado
Fix the IAbsoluteURL issue
1520
    if config.devmode:
1521
        factories.append(
1522
            VHRP('apidoc', APIDocBrowserRequest, APIDocBrowserPublication))
1523
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1524
    # We may also have a private XML-RPC server.
1525
    private_port = None
1526
    for server in config.servers:
1527
        if server.type == 'PrivateXMLRPC':
5088.2.6 by Leonard Richardson
Cleanup in response to review.
1528
            ip, private_port = server.address
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1529
            break
1530
1531
    if private_port is not None:
1532
        factories.append(XMLRPCRequestPublicationFactory(
1533
            'xmlrpc_private', PrivateXMLRPCRequest,
1534
            PrivateXMLRPCPublication, port=private_port))
1535
5088.2.6 by Leonard Richardson
Cleanup in response to review.
1536
    # Register those factories, in priority order corresponding to
1537
    # their order in the list. This means picking a large number for
1538
    # the first factory and giving each subsequent factory the next
1539
    # lower number. We need to leave one space left over for the
1540
    # catch-all handler defined below, so we start at
1541
    # len(factories)+1.
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1542
    for priority, factory in enumerate(factories):
1543
        publisher_factory_registry.register(
13125.1.2 by Brad Crittenden
Fixed lint
1544
            "*", "*", factory.vhost_name, len(factories) - priority + 1,
1545
            factory)
5088.2.5 by Leonard Richardson
Revised in response to flacoste feedback
1546
1547
    # Register a catch-all "not found" handler at the lowest priority.
5088.2.2 by Leonard Richardson
Got all the tests to pass except the one that wasn't passing before.
1548
    publisher_factory_registry.register(
1549
        "*", "*", "*", 0, NotFoundRequestPublicationFactory())
5088.2.6 by Leonard Richardson
Cleanup in response to review.
1550
5088.2.1 by Leonard Richardson
Mysterious test and code from another dimension.
1551
register_launchpad_request_publication_factories()