This file is indexed.

/usr/lib/python3/dist-packages/maasserver/webapp.py is in python3-django-maas 2.4.0~beta2-6865-gec43e47e6-0ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# Copyright 2014-2017 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""The MAAS Web Application."""

__all__ = [
    "WebApplicationService",
]

import copy
from functools import partial
from http.client import SERVICE_UNAVAILABLE
import re

from django.conf import settings
from lxml import html
from maasserver import concurrency
from maasserver.utils.threads import deferToDatabase
from maasserver.utils.views import WebApplicationHandler
from maasserver.websockets.protocol import WebSocketFactory
from maasserver.websockets.websockets import (
    lookupProtocolForFactory,
    WebSocketsResource,
)
from metadataserver.api_twisted import StatusHandlerResource
from provisioningserver.logger import LegacyLogger
from provisioningserver.utils.twisted import (
    asynchronous,
    reducedWebLogFormatter,
    ThreadPoolLimiter,
)
from twisted.application.internet import StreamServerEndpointService
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks
from twisted.python import failure
from twisted.web.error import UnsupportedMethod
from twisted.web.resource import (
    ErrorPage,
    NoResource,
    Resource,
)
from twisted.web.server import (
    Request,
    Site,
)
from twisted.web.static import File
from twisted.web.util import Redirect
from twisted.web.wsgi import WSGIResource


log = LegacyLogger()


class StartPage(ErrorPage, object):
    def __init__(self):
        super(StartPage, self).__init__(
            status=int(SERVICE_UNAVAILABLE), brief="MAAS is starting",
            detail="Please try again in a few seconds.")

    def render(self, request):
        request.setHeader(b"Retry-After", b"5")
        return super(StartPage, self).render(request)


class StartFailedPage(ErrorPage, object):

    def __init__(self, failure):
        traceback = html.Element("pre")
        traceback.text = failure.getTraceback()
        super(StartFailedPage, self).__init__(
            status=int(SERVICE_UNAVAILABLE), brief="MAAS failed to start",
            detail=html.tostring(traceback, encoding=str))


class CleanPathRequest(Request, object):
    """A request that supports '/+' in the path.

    It converts all '/+' in the path to a single '/'.
    """

    def requestReceived(self, command, path, version):
        path, sep, args = path.partition(b"?")
        path = re.sub(rb'/+', b'/', path)
        path = b"".join([path, sep, args])
        return super(CleanPathRequest, self).requestReceived(
            command, path, version)


class OverlaySite(Site):
    """A site that is over another site.

    If this site cannot resolve a valid resource to handle the request, then
    the underlay site gets passed the request to process.
    """

    underlay = None

    def getResourceFor(self, request):
        """Override to support an underlay site.

        If this site cannot return a valid resource to request is passed to
        the underlay site to resolve the request.
        """
        def call_underlay(request):
            # Reset the paths and forward to the underlay site.
            request.prepath = []
            request.postpath = postpath
            return self.underlay.getResourceFor(request)

        def wrap_render(orig_render, request):
            # Wrap the render call of the resource, catching any
            # UnsupportedMethod exceptions and forwarding those onto
            # the underlay site.
            try:
                return orig_render(request)
            except UnsupportedMethod:
                if self.underlay is not None:
                    resource = call_underlay(request)
                    return resource.render(request)
                else:
                    raise

        postpath = copy.copy(request.postpath)
        result = super(OverlaySite, self).getResourceFor(request)
        if isinstance(result, NoResource) and self.underlay is not None:
            return call_underlay(request)
        else:
            if hasattr(result.render, '__overlay_wrapped__'):
                # Render method for the resulting resource has already been
                # wrapped, so don't wrap it again.
                return result
            else:
                # First time this resource has been returned. Wrap the render
                # method so if the resource doesn't support that method
                # it will be passed to the underlay.
                result.render = partial(wrap_render, result.render)
                result.render.__overlay_wrapped__ = True
            return result


class ResourceOverlay(Resource, object):
    """A resource that can fall-back to a basis resource.

    Children can be set using `putChild()` as usual. However, if path
    traversal doesn't find one of these children, the `basis` resource is
    returned, and path traversal will then be tried again through that it. In
    addition, if path traversal results in this resource, rendering will also
    be passed-through to the `basis` resource.

    :ivar basis: An `IResource`.
    """

    def __init__(self, basis):
        super(ResourceOverlay, self).__init__()
        self.basis = basis

    def getChild(self, path, request):
        """Return the basis resource.

        Also undo the path traversal that brought us here so that the basis
        resource can be asked for it.
        """
        # Move back up one level in path traversal.
        request.postpath.insert(0, path)
        request.prepath.pop()
        # Traversal will continue with the basis resource.
        return self.basis

    def render(self, request):
        """Pass-through to the basis resource."""
        return self.basis.render(request)


class WebApplicationService(StreamServerEndpointService):
    """Service encapsulating the Django web application.

    This shows a default "MAAS is starting" web page until Django is up. If
    Django cannot be started, the page is replaced by the error that caused
    start-up to fail.

    :ivar site: The site object that wraps a WSGI resource.
    :ivar threadpool: The thread-pool used for servicing requests to
        the web application.
    """

    def __init__(self, endpoint, listener, status_worker):
        self.site = OverlaySite(
            StartPage(), logFormatter=reducedWebLogFormatter, timeout=None)
        self.site.requestFactory = CleanPathRequest
        super(WebApplicationService, self).__init__(endpoint, self.site)
        self.websocket = WebSocketFactory(listener)
        self.threadpool = ThreadPoolLimiter(
            reactor.threadpoolForDatabase, concurrency.webapp)
        self.status_worker = status_worker

    def prepareApplication(self):
        """Return the WSGI application.

        If we run servers on multiple endpoints this ought to be extracted
        into a separate function, so that each server uses the same
        application.
        """
        return WebApplicationHandler()

    def startWebsocket(self):
        """Start the websocket factory for the `WebSocketsResource`."""
        self.websocket.startFactory()

    def installApplication(self, application):
        """Install the WSGI application into the Twisted site.

        It's installed as a child with path "MAAS". This matches the default
        front-end configuration (i.e. Apache) so that there's no need to force
        script names.
        """
        # Setup resources to process paths that twisted handles.
        metadata = Resource()
        metadata.putChild(b'status', StatusHandlerResource(self.status_worker))

        maas = Resource()
        maas.putChild(b'metadata', metadata)
        maas.putChild(b'static', File(settings.STATIC_ROOT))
        maas.putChild(
            b'ws',
            WebSocketsResource(lookupProtocolForFactory(self.websocket)))

        root = Resource()
        root.putChild(b'', Redirect(b"MAAS/"))
        root.putChild(b'MAAS', maas)

        # Setup the resources to process paths that django handles.
        underlay_maas = ResourceOverlay(
            WSGIResource(reactor, self.threadpool, application))
        underlay_root = Resource()
        underlay_root.putChild(b'MAAS', underlay_maas)
        underlay_site = Site(
            underlay_root, logFormatter=reducedWebLogFormatter)
        underlay_site.requestFactory = CleanPathRequest

        # Setup the main resource as the twisted handler and the underlay
        # resource as the django handler.
        self.site.resource = root
        self.site.underlay = underlay_site

    def installFailed(self, failure):
        """Display a page explaining why the web app could not start."""
        self.site.resource = StartFailedPage(failure)
        log.err(failure, "MAAS web application failed to start")

    @inlineCallbacks
    def startApplication(self):
        """Start the Django application, and install it."""
        try:
            application = yield deferToDatabase(self.prepareApplication)
            self.startWebsocket()
            self.installApplication(application)
        except:
            self.installFailed(failure.Failure())

    @asynchronous(timeout=30)
    def startService(self):
        super(WebApplicationService, self).startService()
        return self.startApplication()

    @asynchronous(timeout=30)
    def stopService(self):
        d = super(WebApplicationService, self).stopService()
        d.addCallback(lambda _: self.websocket.stopFactory())
        return d