Example #1
0
    def test_headers_type(self):
        r = HttpResponse()

        # The following tests explicitly test types in addition to values
        # because in Python 2 u'foo' == b'foo'.

        # ASCII unicode or bytes values are converted to native strings.
        r['key'] = 'test'
        self.assertEqual(r['key'], str('test'))
        self.assertIsInstance(r['key'], str)
        r['key'] = 'test'.encode('ascii')
        self.assertEqual(r['key'], str('test'))
        self.assertIsInstance(r['key'], str)
        self.assertIn(b'test', r.serialize_headers())

        # Latin-1 unicode or bytes values are also converted to native strings.
        r['key'] = 'café'
        self.assertEqual(r['key'], force_str('café', 'latin-1'))
        self.assertIsInstance(r['key'], str)
        r['key'] = 'café'.encode('latin-1')
        self.assertEqual(r['key'], force_str('café', 'latin-1'))
        self.assertIsInstance(r['key'], str)
        self.assertIn('café'.encode('latin-1'), r.serialize_headers())

        # Other unicode values are MIME-encoded (there's no way to pass them as bytes).
        r['key'] = '†'
        self.assertEqual(r['key'], str('=?utf-8?b?4oCg?='))
        self.assertIsInstance(r['key'], str)
        self.assertIn(b'=?utf-8?b?4oCg?=', r.serialize_headers())

        # The response also converts unicode or bytes keys to strings, but requires
        # them to contain ASCII
        r = HttpResponse()
        del r['Content-Type']
        r['foo'] = 'bar'
        l = list(r.items())
        self.assertEqual(len(l), 1)
        self.assertEqual(l[0], ('foo', 'bar'))
        self.assertIsInstance(l[0][0], str)

        r = HttpResponse()
        del r['Content-Type']
        r[b'foo'] = 'bar'
        l = list(r.items())
        self.assertEqual(len(l), 1)
        self.assertEqual(l[0], ('foo', 'bar'))
        self.assertIsInstance(l[0][0], str)

        r = HttpResponse()
        with self.assertRaises(UnicodeError):
            r.__setitem__('føø', 'bar')
        with self.assertRaises(UnicodeError):
            r.__setitem__('føø'.encode('utf-8'), 'bar')
Example #2
0
    def test_headers_type(self):
        r = HttpResponse()

        # The following tests explicitly test types in addition to values
        # because in Python 2 u'foo' == b'foo'.

        # ASCII unicode or bytes values are converted to native strings.
        r['key'] = 'test'
        self.assertEqual(r['key'], str('test'))
        self.assertIsInstance(r['key'], str)
        r['key'] = 'test'.encode('ascii')
        self.assertEqual(r['key'], str('test'))
        self.assertIsInstance(r['key'], str)
        self.assertIn(b'test', r.serialize_headers())

        # Latin-1 unicode or bytes values are also converted to native strings.
        r['key'] = 'café'
        self.assertEqual(r['key'], force_str('café', 'latin-1'))
        self.assertIsInstance(r['key'], str)
        r['key'] = 'café'.encode('latin-1')
        self.assertEqual(r['key'], force_str('café', 'latin-1'))
        self.assertIsInstance(r['key'], str)
        self.assertIn('café'.encode('latin-1'), r.serialize_headers())

        # Other unicode values are MIME-encoded (there's no way to pass them as bytes).
        r['key'] = '†'
        self.assertEqual(r['key'], str('=?utf-8?b?4oCg?='))
        self.assertIsInstance(r['key'], str)
        self.assertIn(b'=?utf-8?b?4oCg?=', r.serialize_headers())

        # The response also converts unicode or bytes keys to strings, but requires
        # them to contain ASCII
        r = HttpResponse()
        del r['Content-Type']
        r['foo'] = 'bar'
        headers = list(r.items())
        self.assertEqual(len(headers), 1)
        self.assertEqual(headers[0], ('foo', 'bar'))
        self.assertIsInstance(headers[0][0], str)

        r = HttpResponse()
        del r['Content-Type']
        r[b'foo'] = 'bar'
        headers = list(r.items())
        self.assertEqual(len(headers), 1)
        self.assertEqual(headers[0], ('foo', 'bar'))
        self.assertIsInstance(headers[0][0], str)

        r = HttpResponse()
        with self.assertRaises(UnicodeError):
            r.__setitem__('føø', 'bar')
        with self.assertRaises(UnicodeError):
            r.__setitem__('føø'.encode('utf-8'), 'bar')
Example #3
0
    def test_headers_type(self):
        r = HttpResponse()

        # The following tests explicitly test types in addition to values
        # because in Python 2 u'foo' == b'foo'.

        # ASCII unicode or bytes values are converted to native strings.
        r["key"] = "test"
        self.assertEqual(r["key"], str("test"))
        self.assertIsInstance(r["key"], str)
        r["key"] = "test".encode("ascii")
        self.assertEqual(r["key"], str("test"))
        self.assertIsInstance(r["key"], str)
        self.assertIn(b"test", r.serialize_headers())

        # Latin-1 unicode or bytes values are also converted to native strings.
        r["key"] = "café"
        self.assertEqual(r["key"], force_str("café", "latin-1"))
        self.assertIsInstance(r["key"], str)
        r["key"] = "café".encode("latin-1")
        self.assertEqual(r["key"], force_str("café", "latin-1"))
        self.assertIsInstance(r["key"], str)
        self.assertIn("café".encode("latin-1"), r.serialize_headers())

        # Other unicode values are MIME-encoded (there's no way to pass them as bytes).
        r["key"] = "†"
        self.assertEqual(r["key"], str("=?utf-8?b?4oCg?="))
        self.assertIsInstance(r["key"], str)
        self.assertIn(b"=?utf-8?b?4oCg?=", r.serialize_headers())

        # The response also converts unicode or bytes keys to strings, but requires
        # them to contain ASCII
        r = HttpResponse()
        del r["Content-Type"]
        r["foo"] = "bar"
        l = list(r.items())
        self.assertEqual(len(l), 1)
        self.assertEqual(l[0], ("foo", "bar"))
        self.assertIsInstance(l[0][0], str)

        r = HttpResponse()
        del r["Content-Type"]
        r[b"foo"] = "bar"
        l = list(r.items())
        self.assertEqual(len(l), 1)
        self.assertEqual(l[0], ("foo", "bar"))
        self.assertIsInstance(l[0][0], str)

        r = HttpResponse()
        with self.assertRaises(UnicodeError):
            r.__setitem__("føø", "bar")
        with self.assertRaises(UnicodeError):
            r.__setitem__("føø".encode("utf-8"), "bar")
Example #4
0
    def test_headers_type(self):
        r = HttpResponse()

        # ASCII strings or bytes values are converted to strings.
        r.headers['key'] = 'test'
        self.assertEqual(r.headers['key'], 'test')
        r.headers['key'] = b'test'
        self.assertEqual(r.headers['key'], 'test')
        self.assertIn(b'test', r.serialize_headers())

        # Non-ASCII values are serialized to Latin-1.
        r.headers['key'] = 'café'
        self.assertIn('café'.encode('latin-1'), r.serialize_headers())

        # Other Unicode values are MIME-encoded (there's no way to pass them as
        # bytes).
        r.headers['key'] = '†'
        self.assertEqual(r.headers['key'], '=?utf-8?b?4oCg?=')
        self.assertIn(b'=?utf-8?b?4oCg?=', r.serialize_headers())

        # The response also converts string or bytes keys to strings, but requires
        # them to contain ASCII
        r = HttpResponse()
        del r.headers['Content-Type']
        r.headers['foo'] = 'bar'
        headers = list(r.headers.items())
        self.assertEqual(len(headers), 1)
        self.assertEqual(headers[0], ('foo', 'bar'))

        r = HttpResponse()
        del r.headers['Content-Type']
        r.headers[b'foo'] = 'bar'
        headers = list(r.headers.items())
        self.assertEqual(len(headers), 1)
        self.assertEqual(headers[0], ('foo', 'bar'))
        self.assertIsInstance(headers[0][0], str)

        r = HttpResponse()
        with self.assertRaises(UnicodeError):
            r.headers.__setitem__('føø', 'bar')
        with self.assertRaises(UnicodeError):
            r.headers.__setitem__('føø'.encode(), 'bar')
Example #5
0
    def test_headers_type(self):
        r = HttpResponse()

        # ASCII strings or bytes values are converted to strings.
        r.headers["key"] = "test"
        self.assertEqual(r.headers["key"], "test")
        r.headers["key"] = b"test"
        self.assertEqual(r.headers["key"], "test")
        self.assertIn(b"test", r.serialize_headers())

        # Non-ASCII values are serialized to Latin-1.
        r.headers["key"] = "café"
        self.assertIn("café".encode("latin-1"), r.serialize_headers())

        # Other Unicode values are MIME-encoded (there's no way to pass them as
        # bytes).
        r.headers["key"] = "†"
        self.assertEqual(r.headers["key"], "=?utf-8?b?4oCg?=")
        self.assertIn(b"=?utf-8?b?4oCg?=", r.serialize_headers())

        # The response also converts string or bytes keys to strings, but requires
        # them to contain ASCII
        r = HttpResponse()
        del r.headers["Content-Type"]
        r.headers["foo"] = "bar"
        headers = list(r.headers.items())
        self.assertEqual(len(headers), 1)
        self.assertEqual(headers[0], ("foo", "bar"))

        r = HttpResponse()
        del r.headers["Content-Type"]
        r.headers[b"foo"] = "bar"
        headers = list(r.headers.items())
        self.assertEqual(len(headers), 1)
        self.assertEqual(headers[0], ("foo", "bar"))
        self.assertIsInstance(headers[0][0], str)

        r = HttpResponse()
        with self.assertRaises(UnicodeError):
            r.headers.__setitem__("føø", "bar")
        with self.assertRaises(UnicodeError):
            r.headers.__setitem__("føø".encode(), "bar")
Example #6
0
    def test_headers_type(self):
        r = HttpResponse()

        # ASCII strings or bytes values are converted to strings.
        r['key'] = 'test'
        self.assertEqual(r['key'], 'test')
        r['key'] = 'test'.encode('ascii')
        self.assertEqual(r['key'], 'test')
        self.assertIn(b'test', r.serialize_headers())

        # Non-ASCII values are serialized to Latin-1.
        r['key'] = 'café'
        self.assertIn('café'.encode('latin-1'), r.serialize_headers())

        # Other unicode values are MIME-encoded (there's no way to pass them as bytes).
        r['key'] = '†'
        self.assertEqual(r['key'], '=?utf-8?b?4oCg?=')
        self.assertIn(b'=?utf-8?b?4oCg?=', r.serialize_headers())

        # The response also converts string or bytes keys to strings, but requires
        # them to contain ASCII
        r = HttpResponse()
        del r['Content-Type']
        r['foo'] = 'bar'
        headers = list(r.items())
        self.assertEqual(len(headers), 1)
        self.assertEqual(headers[0], ('foo', 'bar'))

        r = HttpResponse()
        del r['Content-Type']
        r[b'foo'] = 'bar'
        headers = list(r.items())
        self.assertEqual(len(headers), 1)
        self.assertEqual(headers[0], ('foo', 'bar'))
        self.assertIsInstance(headers[0][0], str)

        r = HttpResponse()
        with self.assertRaises(UnicodeError):
            r.__setitem__('føø', 'bar')
        with self.assertRaises(UnicodeError):
            r.__setitem__('føø'.encode(), 'bar')
Example #7
0
    def test_middleware_with_no_cache_headers(self):
        """
        Ensure that the middleware does not rewrite the response if it does
        not contains Cache headers.
        """
        response = HttpResponse("OK")
        expected_headers = response.serialize_headers()

        with self.settings(MAX_BROWSER_CACHE_TTL=1):
            middleware = LimitBrowserCacheTTLHeaders(lambda request: response)
            response_to_test = middleware(HttpRequest())
            self.assertEqual(expected_headers, response_to_test.serialize_headers())
Example #8
0
    def test_middleware_limit_not_reached(self):
        """
        Ensure that the middleware does not rewrite the response if the cache
        headers timeout are lower than MAX_BROWSER_CACHE_TTL
        """
        response = HttpResponse("OK")
        patch_response_headers(response, cache_timeout=30)
        expected_headers = response.serialize_headers()
        self.assertIn("max-age=30", response["Cache-Control"])

        with self.settings(MAX_BROWSER_CACHE_TTL=60):
            middleware = LimitBrowserCacheTTLHeaders(lambda request: response)
            response_to_test = middleware(HttpRequest())
            self.assertEqual(expected_headers, response_to_test.serialize_headers())
Example #9
0
def pki_request(request, resource_url=None):
    """
    App's main view
    :param request: Django request object
    :type request: django.http.HttpRequest
    :param resource_url: Remainder of parsed path, e.g. '/pki/<resource_url>'
    :rtype: HttpResponse
    """

    # Limit to allowed host calls, e.g. when coming from local Py packages
    req_host = request.get_host()
    if not req_host:
        return HttpResponse("Host missing for service request.",
                            status=403,
                            content_type="text/plain")
    else:
        req_host = req_host.split(':')[0]  # remove any port
    logger.debug("request host: {0}".format(req_host))
    site_url = urlsplit(settings.SITEURL)
    exch_url = urlsplit(settings.SITE_LOCAL_URL)
    allowed_hosts = [
        'localhost', '127.0.0.1', '[::1]', 'testserver', site_url.hostname,
        exch_url.hostname
    ]
    logger.debug("allowed_hosts: {0}".format(allowed_hosts))
    if not validate_host(req_host, allowed_hosts):
        return HttpResponse("Host requesting service is not allowed.",
                            status=403,
                            content_type="text/plain")

    if not resource_url:
        return HttpResponse('Resource URL missing for PKI request',
                            status=400,
                            content_type='text/plain')

    logger.info(
        "PKI view starting with resource url: {0}".format(resource_url))

    # Manually copy over headers, skipping unwanted ones
    logger.info("PKI view request.COOKIES: {0}".format(request.COOKIES))
    logger.info("PKI view request.META: {0}".format(request.META))
    # IMPORTANT: Don't pass any cookies or OAuth2 headers to remote resource
    headers = {}
    if request.method in ("POST", "PUT") and "CONTENT_TYPE" in request.META:
        headers["Content-Type"] = request.META["CONTENT_TYPE"]

    # Pass through HTTP_ACCEPT* headers
    accepts = ['Accept', 'Accept-Encoding', 'Accept-Language']
    # Why no support for Accept-Charset, i.e. no HTTP_ACCEPT_CHARSET?
    http_accepts = [
        'HTTP_ACCEPT', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE'
    ]
    for accept, http_accept in zip(accepts, http_accepts):
        if http_accept in request.META:
            headers[accept] = request.META[http_accept]

    # TODO: Passthru HTTP_REFERER?

    # Strip our bearer token header!
    auth_header = request.META.get('HTTP_AUTHORIZATION', None)
    # TODO: Passing a bearer token for a remote service should be considered;
    #       add a comparison of our get_bearer_token(), delete only on match
    if auth_header and 'bearer' in auth_header.lower():
        del request.META['HTTP_AUTHORIZATION']

    # Strip our bearer token token from query params!
    # TODO: Migrate to request.GET QueryDict parsing?
    #       Unsure if keep_blank_values and doseq are supported
    query_str = request.META['QUERY_STRING']
    query = None
    if query_str != '':
        # Keep keys with empty values and maintain order
        params = parse_qsl(query_str.strip(), keep_blank_values=True)
        clean_params = [(k, v) for k, v in params
                        if k.lower() != 'access_token']
        query = urlencode(clean_params, doseq=True)

    # Turn the remainder of path back into original URL
    r_url = unquote(resource_url)
    # NOTE: Since no origin scheme is recorded (could be in rewritten pki
    # proxy path), assume https
    url = 'https://' + r_url + (('?' + query) if query else '')

    logger.info("PKI view starting remote connection to url: {0}".format(url))

    # Do remote request
    logger.info("PKI view 'requests' request headers:\n{0}".format(headers))
    req_res = https_client.request(
        method=request.method,
        url=url,
        headers=headers,
        data=request.body,
    )
    """:type: requests.Response"""

    if not req_res:
        return HttpResponse('Remote service did not return content.',
                            status=400,
                            content_type='text/plain')

    # TODO: Capture errors and signal to web UI for reporting to user.
    #       Don't let errors just raise exceptions

    logger.info("PKI view 'requests' response status code: {0}".format(
        req_res.status_code))
    logger.info("PKI view 'requests' response headers:\n{0}".format(
        req_res.headers))

    if query and ('f=pjson' in query or 'f=json' in query):
        # Sometimes arcrest servers don't return proper content type
        content_type = 'application/json'
    elif 'Content-Type' in req_res.headers:
        content_type = req_res.headers['Content-Type']
    else:
        content_type = 'text/plain'

    req_transfer_encodings = ['gzip', 'deflate']

    # If we get a redirect, beyond # allowed in config, add a useful message.
    if req_res.status_code in (301, 302, 303, 307):
        response = HttpResponse(
            'This proxy does not support more than the configured redirects. '
            'The server in "{0}" asked for this recent redirect: "{1}"'.format(
                url, req_res.headers.get('Location')),
            status=req_res.status_code,
            content_type=content_type)
        response['Location'] = req_res.headers['Location']
    else:
        # logger.debug("pki requests response content (first 2000 chars):\n{0}"
        #              .format(req_res.content[:2000]))
        txt_content = 'Not textual content'
        txt_types = ['text', 'json', 'xml']
        if any([t in content_type.lower() for t in txt_types]):
            txt_content = req_res.content
            # Format JSON as needed for client log output
            if ('json' in content_type.lower()
                    and logger.getEffectiveLevel() <= logging.INFO
                    and callable(logging_timer_expired)
                    and not logging_timer_expired()):
                txt_content = json.dumps(json.loads(txt_content), indent=2)

        logger.info(
            "PKI view 'requests' response content:\n{0}".format(txt_content))

        if req_res.headers.get('content-encoding') in req_transfer_encodings:
            # Change content length to reflect requests auto-decompression
            req_res.headers['content-length'] = len(req_res.content)

        response = HttpResponse(
            content=req_res.content,
            status=req_res.status_code,
            reason=req_res.reason,
            content_type=content_type,
        )

    # TODO: Should we be sniffing encoding/charset and passing back?

    # Passthru headers from remote service, but don't overwrite defined headers
    def skip_content_encoding(hdr, val):
        # requests automatically decodes gzip and deflate transfer encodings;
        # ensure header is not passed through to client
        # http://docs.python-requests.org/en/master/user/quickstart/
        #   #binary-response-content
        if hdr.lower() == 'content-encoding' and val in req_transfer_encodings:
            return True
        return False

    skip_headers = ['content-type']  # add any as lowercase
    for h, v in req_res.headers.items():
        if h.lower() not in skip_headers \
                and not skip_content_encoding(h, v) \
                and not wsgiref_util.is_hop_by_hop(h)\
                and h not in response:
            response[h] = v

    logger.info("PKI view Django response headers:\n{0}".format(
        response.serialize_headers()))

    return response