Exemplo n.º 1
0
    def renderFile(self, request: TwistedRequest, zipFile: ZipFile,
                   info: ZipInfo) -> object:
        """Serve a file entry from a ZIP file.
        """

        # Determine content type.
        contentType, encoding = guess_type(info.filename, strict=False)
        if encoding is not None:
            # We want contents to be served as-is, not automatically
            # decoded by the browser.
            contentType = 'application/' + encoding
        if contentType is None:
            contentType = 'application/octet-stream'
        request.setHeader(b'Content-Type', contentType.encode())
        request.setHeader(b'Content-Disposition', b'inline')

        # Serve data in compressed form if user agent accepts it.
        if info.compress_type == ZIP_DEFLATED:
            accept = AcceptedEncodings.parse(
                request.getHeader('accept-encoding'))
            decompress = 4.0 * accept['gzip'] < accept['identity']
            if not decompress:
                request.setHeader('Content-Encoding', 'gzip')
        else:
            decompress = True
        if decompress:
            FileProducer.servePlain(zipFile.open(info), request)
        else:
            FileProducer.serveZipEntry(self.zipPath.open(), info, request)
        return NOT_DONE_YET
Exemplo n.º 2
0
def verifySignature(request: TwistedRequest,
                    payload: bytes,
                    secret: bytes
                    ) -> bool:
    remoteSig = request.getHeader('X-Gogs-Signature')
    if not remoteSig:
        return False
    localSig = hmac.new(secret, payload, 'sha256').hexdigest()
    return hmac.compare_digest(localSig.casefold(), remoteSig.casefold())
Exemplo n.º 3
0
    async def _async_render_POST(self, request: Request) -> None:
        requester = await self.auth.get_user_by_req(request)
        # TODO: The checks here are a bit late. The content will have
        # already been uploaded to a tmp file at this point
        content_length = request.getHeader("Content-Length")
        if content_length is None:
            raise SynapseError(msg="Request must specify a Content-Length",
                               code=400)
        if int(content_length) > self.max_upload_size:
            raise SynapseError(
                msg="Upload request body is too large",
                code=413,
                errcode=Codes.TOO_LARGE,
            )

        upload_name = parse_string(request, b"filename", encoding=None)
        if upload_name:
            try:
                upload_name = upload_name.decode("utf8")
            except UnicodeDecodeError:
                raise SynapseError(msg="Invalid UTF-8 filename parameter: %r" %
                                   (upload_name),
                                   code=400)

        # If the name is falsey (e.g. an empty byte string) ensure it is None.
        else:
            upload_name = None

        headers = request.requestHeaders

        if headers.hasHeader(b"Content-Type"):
            content_type_headers = headers.getRawHeaders(b"Content-Type")
            assert content_type_headers  # for mypy
            media_type = content_type_headers[0].decode("ascii")
        else:
            raise SynapseError(msg="Upload request missing 'Content-Type'",
                               code=400)

        # if headers.hasHeader(b"Content-Disposition"):
        #     disposition = headers.getRawHeaders(b"Content-Disposition")[0]
        # TODO(markjh): parse content-dispostion

        try:
            content = request.content  # type: IO  # type: ignore
            content_uri = await self.media_repo.create_content(
                media_type, upload_name, content, content_length,
                requester.user)
        except SpamMediaException:
            # For uploading of media we want to respond with a 400, instead of
            # the default 404, as that would just be confusing.
            raise SynapseError(400, "Bad content")

        logger.info("Uploaded content with URI %r", content_uri)

        respond_with_json(request,
                          200, {"content_uri": content_uri},
                          send_cors=True)
Exemplo n.º 4
0
    def render_GET(self, request: Request):  # $ requestHandler
        # see https://twistedmatrix.com/documents/21.2.0/api/twisted.web.server.Request.html
        ensure_tainted(
            request,  # $ tainted
            request.uri,  # $ tainted
            request.path,  # $ tainted
            request.prepath,  # $ tainted
            request.postpath,  # $ tainted

            # file-like
            request.content,  # $ tainted
            request.content.read(),  # $ MISSING: tainted

            # Dict[bytes, List[bytes]] (for query args)
            request.args,  # $ tainted
            request.args[b"key"],  # $ tainted
            request.args[b"key"][0],  # $ tainted
            request.args.get(b"key"),  # $ tainted
            request.args.get(b"key")[0],  # $ tainted
            request.received_cookies,  # $ tainted
            request.received_cookies["key"],  # $ tainted
            request.received_cookies.get("key"),  # $ tainted
            request.getCookie(b"key"),  # $ tainted

            # twisted.web.http_headers.Headers
            # see https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http_headers.Headers.html
            request.requestHeaders,  # $ tainted
            request.requestHeaders.getRawHeaders("key"),  # $ MISSING: tainted
            request.requestHeaders.getRawHeaders("key")
            [0],  # $ MISSING: tainted
            request.requestHeaders.getAllRawHeaders(),  # $ MISSING: tainted
            list(request.requestHeaders.getAllRawHeaders()
                 ),  # $ MISSING: tainted
            request.getHeader("key"),  # $ tainted
            request.getAllHeaders(),  # $ tainted
            request.getAllHeaders()["key"],  # $ tainted
            request.user,  # $ tainted
            request.getUser(),  # $ tainted
            request.password,  # $ tainted
            request.getPassword(),  # $ tainted
            request.host,  # $ tainted
            request.getHost(),  # $ tainted
            request.getRequestHostname(),  # $ tainted
        )

        # technically user-controlled, but unlikely to lead to vulnerabilities.
        ensure_not_tainted(request.method, )

        # not tainted at all
        ensure_not_tainted(
            # outgoing things
            request.cookies,
            request.responseHeaders,
        )
Exemplo n.º 5
0
    def render_OPTIONS(self, request: TwistedRequest) -> bytes:
        # Generic HTTP options.
        request.setHeader(b'Allow', b'GET, HEAD, OPTIONS')

        # CORS options.
        origin = request.getHeader(b'Origin')
        if origin is not None:
            # Grant all requested headers.
            requestedHeaders = request.getHeader(
                b'Access-Control-Request-Headers') or b''
            request.setHeader(b'Access-Control-Allow-Headers',
                              requestedHeaders)

            # The information returned does not expire, but the sanboxed URL
            # to which it applies does, so caching it beyond the sanbox key
            # timeout is pointless.
            request.setHeader(b'Access-Control-Max-Age',
                              b'%d' % ArtifactSandbox.keyTimeout)

        # Reply without content.
        request.setResponseCode(204)
        request.setHeader(b'Content-Length', b'0')
        return b''
Exemplo n.º 6
0
def verifySignature(request: TwistedRequest, payload: bytes,
                    secret: bytes) -> bool:
    sigHeader = request.getHeader('X-Hub-Signature')
    if not sigHeader:
        return False
    try:
        hashName, remoteSig = sigHeader.split('=', 1)
    except ValueError:
        return False
    if hashName != 'sha1':
        # Only accept the SHA1 hash function that GitHub currently uses.
        return False
    localSig = hmac.new(secret, payload, hashName).hexdigest()
    return hmac.compare_digest(localSig.casefold(), remoteSig.casefold())
Exemplo n.º 7
0
def tokenFromRequest(request: Request) -> Optional[str]:
    """Extract token from header of query parameter.

    :param request: The request to look for an access token in.

    :return: The token or None if not found
    """
    token = None
    # check for Authorization header first
    authHeader = request.getHeader("Authorization")
    if authHeader is not None and authHeader.startswith("Bearer "):
        token = authHeader[len("Bearer "):]

    # no? try access_token query param
    if token is None:
        args = get_args(request, ("access_token", ), required=False)
        token = args.get("access_token")

    return token
Exemplo n.º 8
0
    def render_GET(self, request: TwistedRequest) -> object:
        path = self.path

        contentType, contentEncoding = guess_type(path.basename(),
                                                  strict=False)
        if contentType is None:
            contentType = 'application/octet-stream'
        elif contentType.startswith('text/'):
            # Encoding autodetection in browsers is pretty poor, so we're
            # likely better off forcing UTF-8. Most gzipped text files are
            # logs and we tell the wrappers to output in UTF-8.
            # TODO: Perform an encoding detection on upload, or just preserve
            #       the Content-Type header of the PUT request, if present.
            # TODO: Convert to UTF-8 if the user agent requests it using
            #       the Accept-Charset header. MDN says modern browsers omit
            #       this header, but a non-browser client could set it to
            #       indicate it is only willing to deal with UTF-8.
            #       If no particular encoding is requested, just serve the
            #       file as it was uploaded.
            contentType += '; charset=UTF-8'
        request.setHeader(b'Content-Type', contentType.encode())
        request.setHeader(b'Content-Disposition', b'inline')

        # Serve data in compressed form if user agent accepts it.
        if contentEncoding is None:
            decompress = False
        else:
            accept = AcceptedEncodings.parse(
                request.getHeader('accept-encoding'))
            decompress = 4.0 * accept[contentEncoding] < accept['identity']
            if not decompress:
                request.setHeader('Content-Encoding', contentEncoding)
        if decompress:
            # Note: Passing 'fileobj' to GzipFile appears more elegant,
            #       but that stream isn't closed when GzipFile is closed.
            stream = openGzip(path.path)
        else:
            stream = path.open()

        FileProducer.servePlain(stream, request)
        return NOT_DONE_YET
Exemplo n.º 9
0
    def getChildWithDefault(self, name: bytes,
                            request: TwistedRequest) -> IResource:
        # Prevent leaking sandbox key to external sites.
        request.setHeader(b'Referrer-Policy', b'origin-when-cross-origin')
        # Repeat sandbox rules, in case artifact is viewed outside iframe.
        request.setHeader(b'Content-Security-Policy',
                          b'sandbox %s;' % SANDBOX_RULES.encode())

        origin = request.getHeader(b'Origin')

        # Handle anonymous guest access.
        if name == b'anon' and self.project.anonguest:
            if origin is not None:
                request.setHeader(b'Access-Control-Allow-Origin', b'*')
            return SandboxedResource(self.baseDir, ())

        # Verify that request came from null origin.
        if origin is not None:
            if origin == b'null':
                request.setHeader(b'Access-Control-Allow-Origin', b'null')
            else:
                return AccessDeniedResource(
                    'Sandboxed content requested from non-null origin')

        try:
            key = name.decode('ascii')
        except UnicodeDecodeError:
            return ClientErrorResource('Key contains invalid characters')

        try:
            path = self._activeKeys[key]
        except KeyError:
            # Key does not exist or is no longer valid.
            # Redirect to non-sandboxed path to acquire new key.
            return Redirect(b'/'.join([b'..'] * (len(request.postpath) + 1) +
                                      request.postpath))
        else:
            return SandboxedResource(self.baseDir, path.path)
	def getHeader(self, name, default=None):
		"""Get a request header. If it is not defined use the default as fallback"""
		return Request.getHeader(self, name) or default
Exemplo n.º 11
0
    def render(self, request: server.Request) -> bytes:
        # Deny by default.
        request.setResponseCode(401)

        # Get session cookie value if any.
        sessionid = request.getCookie(self.cookie)
        if sessionid is not None:
            if sessionid in self.sessions:
                request.setResponseCode(200)
                self.log.info("Session: Validation succeeded")
                return b""
            else:
                self.log.info("Session: Invalid session id")

        # Token is passed as a query parameter in the original URL.
        origurl = http.urlparse(request.getHeader(self.header))
        query = http.parse_qs(origurl.query)
        args = query.get(self.param, [])
        if len(args) != 1:
            self.log.error("Request: Token {param} missing", param=self.param)
            return b""

        try:
            token = jwt.JWT(key=self.key, jwt=args[0].decode())
        except (jwt.JWTExpired, jwt.JWTNotYetValid, jwt.JWTMissingClaim,
                jwt.JWTInvalidClaimValue, jwt.JWTInvalidClaimFormat,
                jwt.JWTMissingKeyID, jwt.JWTMissingKey) as error:
            self.log.error("JWT token: {error}", error=error)
            return b""
        except Exception:
            self.log.failure("JWT token: Unknown exception")
            return b""

        try:
            claims = json.loads(token.claims)
        except json.JSONDecodeError as error:
            self.log.failure("JWT token: Claims {error}", error=error)
            return b""

        # Collect session parameters from claims.
        sessparams = claims.get("session", {})
        kwargs = {
            "expires": sessparams.get("expires", None),
            "domain": sessparams.get("domain", None),
            "path": sessparams.get("path", None),
            "secure": sessparams.get("secure", None),
            "httpOnly": sessparams.get("httpOnly", None),
            "sameSite": sessparams.get("sameSite", None),
        }

        # Use maxAge for session ttl if it is present, convert it into a str
        # type as required by the addCookie call.
        if "maxAge" in sessparams:
            kwargs["max_age"] = str(sessparams["maxAge"])
            sessttl = int(sessparams["maxAge"])
        else:
            sessttl = self.sessttl

        # Generate a new session id and remember it. Also clean it up after
        # ttl seconds.
        sessionid = secrets.token_urlsafe(nbytes=16).encode()
        self.sessions.add(sessionid)
        reactor.callLater(sessttl, self._session_remove, sessionid)
        self.log.info("Session: Created, num sessions: {sessions}",
                      sessions=len(self.sessions))

        # Set cookie in the browser.
        request.addCookie(self.cookie, sessionid, **kwargs)

        request.setResponseCode(200)
        self.log.info("JWT token: Validation succeeded")
        return b""
Exemplo n.º 12
0
def getEvent(request: TwistedRequest) -> WebhookEvents:
    if request.getHeader('X-Gogs-Event') == 'push':
        return WebhookEvents.PUSH
    else:
        return WebhookEvents.UNSUPPORTED
Exemplo n.º 13
0
def getEvent(request: TwistedRequest) -> WebhookEvents:
    eventName = request.getHeader('X-GitHub-Event')
    return {
        'ping': WebhookEvents.PING,
        'push': WebhookEvents.PUSH,
    }.get(eventName, WebhookEvents.UNSUPPORTED)