Example #1
0
def get_cors_headers(options, request_headers, request_method):
    origins_to_set = get_cors_origins(options, request_headers.get('Origin'))
    headers = CIDict()

    if not origins_to_set:  # CORS is not enabled for this route
        return headers

    for origin in origins_to_set:
        #  TODO, with CIDict, with will only allow one origin
        headers[ACL_ORIGIN] = origin

    headers[ACL_EXPOSE_HEADERS] = options.get('expose_headers')

    if options.get('supports_credentials'):
        headers[ACL_CREDENTIALS] = 'true'  # case sensative

    # This is a preflight request
    # http://www.w3.org/TR/cors/#resource-preflight-requests
    if request_method == 'OPTIONS':
        acl_request_method = request_headers.get(ACL_REQUEST_METHOD,
                                                 '').upper()

        # If there is no Access-Control-Request-Method header or if parsing
        # failed, do not set any additional headers
        if acl_request_method and acl_request_method in options.get('methods'):

            # If method is not a case-sensitive match for any of the values in
            # list of methods do not set any additional headers and terminate
            # this set of steps.
            headers[ACL_ALLOW_HEADERS] = get_allow_headers(
                options, request_headers.get(ACL_REQUEST_HEADERS))
            headers[ACL_MAX_AGE] = str(options.get(
                'max_age'))  # sanic cannot handle integers in header values.
            headers[ACL_METHODS] = options.get('methods')
        else:
            LOG.info(
                "The request's Access-Control-Request-Method header does not match allowed methods. "
                "CORS headers will not be applied.")

    # http://www.w3.org/TR/cors/#resource-implementation
    if options.get('vary_header'):
        # Only set header if the origin returned will vary dynamically,
        # i.e. if we are not returning an asterisk, and there are multiple
        # origins that can be matched.
        if headers[ACL_ORIGIN] == '*':
            pass
        elif (len(options.get('origins')) > 1 or len(origins_to_set) > 1
              or any(map(probably_regex, options.get('origins')))):
            headers['Vary'] = 'Origin'

    return CIDict((k, v) for k, v in headers.items() if v)
Example #2
0
def fake_request(url='/', method='GET', route='/', headers=None, body=None):
    headers = headers or {}
    headers_ = CIDict(host='127.0.0.1')
    [headers_.__setitem__(*x) for x in headers.items()]

    app = Rafter()
    app.add_route(view, route, methods=[method])

    request = app.request_class(url.encode('utf-8'), headers_, '1.1', method,
                                None)
    request.app = app
    request.body = body or b''

    return request
Example #3
0
    async def decorated_filter(request, *args, **kwargs):
        data = {
            'headers': CIDict(request.headers),
            'path': request.app.router.get(request)[2],
            'params': RequestParameters(request.args),
            'body': {}
        }

        if request.body:
            # Get body if we have something there
            if request.form:
                data['body'] = RequestParameters(request.form)
            else:
                # will raise 400 if cannot parse json
                data['body'] = deepcopy(request.json)

        if hasattr(request_schema, 'body') and request.form:
            _convert_params(request_schema.body, data['body'])

        if hasattr(request_schema, 'params') and data['params']:
            _convert_params(request_schema.params, data['params'])

        # Now, validate the whole thing
        try:
            model = request_schema(data, strict=False, validate=False)
            model.validate()
            request.validated = model.to_native()
        except BaseError as e:
            raise ValidationErrors(e.to_primitive())

        return await get_response(request, *args, **kwargs)
Example #4
0
def set_cors_headers(req, resp, options):
    """
    Performs the actual evaluation of Sanic-CORS options and actually
    modifies the response object.

    This function is used both in the decorator and the after_request
    callback
    :param sanic.request.Request req:

    """

    # If CORS has already been evaluated via the decorator, skip
    if isinstance(req.headers,
                  (dict, CIDict)) and SANIC_CORS_EVALUATED in req.headers:
        LOG.debug('CORS have been already evaluated, skipping')
        del req.headers[SANIC_CORS_EVALUATED]
        return resp

    # Some libraries, like OAuthlib, set resp.headers to non Multidict
    # objects (Werkzeug Headers work as well). This is a problem because
    # headers allow repeated values.
    if not isinstance(resp.headers, CIDict):
        resp.headers = CIDict(resp.headers)

    headers_to_set = get_cors_headers(options, req.headers, req.method)

    LOG.debug('Settings CORS headers: %s', str(headers_to_set))

    for k, v in headers_to_set.items():
        resp.headers[k] = v

    return resp
Example #5
0
def fake_request():
    headers = CIDict(host='127.0.0.1')

    app = Rafter()
    app.add_route(view, '/', methods=['GET'])

    request = app.request_class(b'/', headers, '1.1', 'GET', None)
    request.app = app

    return request
Example #6
0
def set_cors_headers(req, resp, context, options):
    """
    Performs the actual evaluation of Sanic-CORS options and actually
    modifies the response object.

    This function is used both in the decorator and the after_request
    callback
    :param sanic.request.Request req:

    """
    try:
        request_context = context.request[id(req)]
    except AttributeError:
        LOG.debug("Cannot find the request context. Is request already finished?")
        return resp
    # If CORS has already been evaluated via the decorator, skip
    evaluated = request_context.get(SANIC_CORS_EVALUATED, False)
    if evaluated:
        LOG.debug('CORS have been already evaluated, skipping')
        return resp

    # `resp` can be None in the case of using Websockets
    # however this case should have been handled in the `extension` and `decorator` methods
    # before getting here. This is a final failsafe check to prevent crashing
    if resp is None:
        return None

    # Some libraries, like OAuthlib, set resp.headers to non Multidict
    # objects (Werkzeug Headers work as well). This is a problem because
    # headers allow repeated values.
    # TODO: In sanic, the CIDict is _not_ a multidict.
    if not isinstance(resp.headers, CIDict):
        # FYI this has the added (bonus) side-effect that if resp.headers is None
        # (response headers can be None on websocket responses for example)
        # Then CIDict(None) actually creates an empty headers dict, that is correct in this situation.
        resp.headers = CIDict(resp.headers)

    headers_to_set = get_cors_headers(options, req.headers, req.method)

    LOG.debug('Settings CORS headers: %s', str(headers_to_set))

    # dict .extend() does not work on CIDict (or multidict) so iterate over them and add them individually.
    for k, v in headers_to_set.items():
        resp.headers[k] = v

    return resp
Example #7
0
def set_cors_headers(req, resp, options):
    """
    Performs the actual evaluation of Sanic-CORS options and actually
    modifies the response object.

    This function is used both in the decorator and the after_request
    callback
    :param sanic.request.Request req:

    """

    # If CORS has already been evaluated via the decorator, skip
    if isinstance(req.headers,
                  (dict, CIDict)) and SANIC_CORS_EVALUATED in req.headers:
        LOG.debug('CORS have been already evaluated, skipping')
        del req.headers[SANIC_CORS_EVALUATED]
        return resp

    # Some libraries, like OAuthlib, set resp.headers to non Multidict
    # objects (Werkzeug Headers work as well). This is a problem because
    # headers allow repeated values.
    # TODO: In sanic, the CIDict is _not_ a multidict.
    if not isinstance(resp.headers, CIDict):
        # FYI this has the added (bonus) side-effect that if resp.headers is None
        # (response headers can be None on websocket responses for example)
        # Then CIDict(None) actually creates an empty headers dict, that is correct in this situation.
        resp.headers = CIDict(resp.headers)

    headers_to_set = get_cors_headers(options, req.headers, req.method)

    LOG.debug('Settings CORS headers: %s', str(headers_to_set))

    # dict .extend() does not work on CIDict (or multidict) so iterate over them and add them individually.
    for k, v in headers_to_set.items():
        resp.headers[k] = v

    return resp
Example #8
0
    def source(self, request):
        '''
        Pulls values off the request in the provided location
        :param request: The flask request object to parse arguments from
        '''
        if isinstance(self.location, six.string_types):
            value = getattr(request, self.location, CIDict())
            if callable(value):
                value = value()
            if value is not None:
                return value
        else:
            values = CIDict()
            for l in self.location:
                value = getattr(request, l, None)
                if callable(value):
                    value = value()
                if value is not None:
                    values.update(value)
            return values

        return CIDict()
Example #9
0
 def test_existing_vary_headers(request):
     return HTTPResponse('',
                         status=200,
                         headers=CIDict({'Vary': 'Accept-Encoding'}))
 def test_multiple_set_cookie_headers(request):
     resp = HTTPResponse(body="Foo bar baz")
     resp.headers = CIDict()
     resp.headers['set-cookie'] = 'foo'
     resp.headers['set-cookie'] = 'bar'
     return resp
Example #11
0
    def handle_error(self, request, e):
        '''
        Error handler for the API transforms a raised exception into a Flask response,
        with the appropriate HTTP status code and body.

        :param Exception e: the raised Exception object

        '''
        # todo: sanic: wtf is this?
        #got_request_exception.send(current_app._get_current_object(), exception=e)
        app = request.app
        headers = CIDict()
        if e.__class__ in self.error_handlers:
            handler = self.error_handlers[e.__class__]
            result = handler(e)
            default_data, code, headers = unpack(result, 500)
        elif isinstance(e, SanicException):
            code = e.status_code
            status = COMMON_STATUS_CODES.get(code)
            if not status:
                status = ALL_STATUS_CODES.get(code)
            if status and isinstance(status, bytes):
                status = status.decode('ascii')
            default_data = {'message': getattr(e, 'message', status)}
            # headers = e.get_response().headers
        elif self._default_error_handler:
            result = self._default_error_handler(e)
            default_data, code, headers = unpack(result, 500)
        else:
            code = 500
            status = COMMON_STATUS_CODES.get(code, str(e))
            if status and isinstance(status, bytes):
                status = status.decode('ascii')
            default_data = {
                'message': status,
            }

        default_data['message'] = default_data.get('message', str(e))
        data = getattr(e, 'data', default_data)
        fallback_mediatype = None

        if code >= 500:
            exc_info = sys.exc_info()
            if exc_info[1] is None:
                exc_info = None
            #current_app.log_exception(exc_info)

        elif code == 404 and app.config.get("ERROR_404_HELP", True):
            data['message'] = self._help_on_404(request,
                                                data.get('message', None))

        elif code == 406 and self.default_mediatype is None:
            # if we are handling NotAcceptable (406), make sure that
            # make_response uses a representation we support as the
            # default mediatype (so that make_response doesn't throw
            # another NotAcceptable error).
            supported_mediatypes = list(self.representations.keys())
            fallback_mediatype = supported_mediatypes[
                0] if supported_mediatypes else "text/plain"

        # Remove blacklisted headers
        for header in HEADERS_BLACKLIST:
            headers.pop(header, None)

        resp = self.make_response(request,
                                  data,
                                  code,
                                  headers,
                                  fallback_mediatype=fallback_mediatype)

        if code == 401:
            resp = self.unauthorized(resp)
        return resp
Example #12
0
    def handle_error(self, request, e):
        '''
        Error handler for the API transforms a raised exception into a Flask response,
        with the appropriate HTTP status code and body.

        :param Exception e: the raised Exception object

        '''
        context = restplus.get_context_from_spf(self.spf_reg)
        app = context.app
        headers = CIDict()
        if e.__class__ in self.error_handlers:
            handler = self.error_handlers[e.__class__]
            result = handler(e)
            default_data, code, headers = unpack(
                result, HTTPStatus.INTERNAL_SERVER_ERROR)
        elif isinstance(e, SanicException):
            code = e.status_code
            if code is 200:
                status = b'OK'
            elif code is 404:
                status = b'Not Found'
            elif code is 500:
                status = b'Internal Server Error'
            else:
                code = HTTPStatus(code)
                status = ALL_STATUS_CODES.get(code.value)
            if status and isinstance(status, bytes):
                status = status.decode('ascii')
            default_data = {'message': getattr(e, 'message', status)}
            # headers = e.get_response().headers
        elif self._default_error_handler:
            result = self._default_error_handler(e)
            default_data, code, headers = unpack(
                result, HTTPStatus.INTERNAL_SERVER_ERROR)
        else:
            code = HTTPStatus.INTERNAL_SERVER_ERROR
            status = ALL_STATUS_CODES.get(code.value, str(e))
            if status and isinstance(status, bytes):
                status = status.decode('ascii')
            default_data = {
                'message': status,
            }

        default_data['message'] = default_data.get('message', str(e))
        data = getattr(e, 'data', default_data)
        fallback_mediatype = None

        if code >= HTTPStatus.INTERNAL_SERVER_ERROR:
            exc_info = sys.exc_info()
            if exc_info[1] is None:
                exc_info = None
            context.log(logging.ERROR, exc_info)

        elif code == HTTPStatus.NOT_FOUND and app.config.get(
                "ERROR_404_HELP", True):
            data['message'] = self._help_on_404(request,
                                                data.get('message', None))

        elif code == HTTPStatus.NOT_ACCEPTABLE and self.default_mediatype is None:
            # if we are handling NotAcceptable (406), make sure that
            # make_response uses a representation we support as the
            # default mediatype (so that make_response doesn't throw
            # another NotAcceptable error).
            supported_mediatypes = list(self.representations.keys())
            fallback_mediatype = supported_mediatypes[
                0] if supported_mediatypes else "text/plain"

        # Remove blacklisted headers
        for header in HEADERS_BLACKLIST:
            headers.pop(header, None)

        resp = self.make_response(request,
                                  data,
                                  code,
                                  headers,
                                  fallback_mediatype=fallback_mediatype)

        if code == HTTPStatus.UNAUTHORIZED:
            resp = self.unauthorized(resp)
        return resp