Пример #1
0
def error(context, request):
    """Catch server errors and trace them."""
    logger.error(context, exc_info=True)

    message = '%s %s: %s' % (context.response.status_code,
                             context.response.reason,
                             context.response.text)
    if context.response.status_code == 400:
        response = http_error(httpexceptions.HTTPBadRequest(),
                              errno=ERRORS.INVALID_PARAMETERS,
                              message=message)
    elif context.response.status_code == 401:
        response = http_error(httpexceptions.HTTPUnauthorized(),
                              errno=ERRORS.INVALID_AUTH_TOKEN,
                              message=message)
        # Forget the current user credentials.
        response.headers.extend(forget(request))
    elif context.response.status_code == 403:
        response = http_error(httpexceptions.HTTPForbidden(),
                              errno=ERRORS.FORBIDDEN,
                              message=message)
    elif context.response.status_code == 404:
        response = http_error(httpexceptions.HTTPNotFound(),
                              errno=ERRORS.INVALID_RESOURCE_ID,
                              message=message)
    else:
        response = service_unavailable(context, request)

    return reapply_cors(request, response)
Пример #2
0
def build_sync_client(request):
    # Get the BID assertion
    is_authorization_defined = AUTHORIZATION_HEADER in request.headers
    starts_with_browser_id = False
    if is_authorization_defined:
        authorization = request.headers[AUTHORIZATION_HEADER].lower()
        starts_with_browser_id = authorization.startswith("browserid")

    if not is_authorization_defined or not starts_with_browser_id:
        error_msg = "Provide a BID assertion %s header." % (
            AUTHORIZATION_HEADER)
        response = http_error(httpexceptions.HTTPUnauthorized(),
                              errno=ERRORS.MISSING_AUTH_TOKEN,
                              message=error_msg)
        response.headers.extend(forget(request))
        raise response

    is_client_state_defined = CLIENT_STATE_HEADER in request.headers
    if not is_client_state_defined:
        error_msg = "Provide the tokenserver %s header." % (
            CLIENT_STATE_HEADER)
        response = http_error(httpexceptions.HTTPUnauthorized(),
                              errno=ERRORS.MISSING_AUTH_TOKEN,
                              message=error_msg)
        response.headers.extend(forget(request))
        raise response

    authorization_header = request.headers[AUTHORIZATION_HEADER]

    bid_assertion = authorization_header.split(" ", 1)[1]
    client_state = request.headers[CLIENT_STATE_HEADER]
    sync_client = SyncClient(bid_assertion, client_state)
    return sync_client
Пример #3
0
def post_batch(request):
    requests = request.validated['requests']
    batch_size = len(requests)

    limit = request.registry.settings['batch_max_requests']
    if limit and len(requests) > int(limit):
        error_msg = 'Number of requests is limited to %s' % limit
        request.errors.add('body', 'requests', error_msg)
        return

    if any([batch.path in req['path'] for req in requests]):
        error_msg = 'Recursive call on %s endpoint is forbidden.' % batch.path
        request.errors.add('body', 'requests', error_msg)
        return

    responses = []

    sublogger = logger.new()

    for subrequest_spec in requests:
        subrequest = build_request(request, subrequest_spec)
        subrequest.parent = request

        sublogger.bind(path=subrequest.path,
                       method=subrequest.method)

        try:
            subresponse = request.invoke_subrequest(subrequest)

        except httpexceptions.HTTPException as e:
            error_msg = 'Failed batch subrequest'
            subresponse = errors.http_error(e, message=error_msg)
        except Exception as e:
            logger.error(e)
            subresponse = errors.http_error(
                httpexceptions.HTTPInternalServerError())

        sublogger.bind(code=subresponse.status_code)
        sublogger.info('subrequest.summary')

        subresponse = build_response(subresponse, subrequest)
        responses.append(subresponse)

    # Rebing batch request for summary
    logger.bind(path=batch.path,
                method=request.method,
                batch_size=batch_size,
                agent=request.headers.get('User-Agent'),)

    return {
        'responses': responses
    }
Пример #4
0
def response_error(context, request):
    """Catch response error from Sync and trace them."""
    message = '%s %s: %s' % (context.response.status_code,
                             context.response.reason,
                             context.response.text)

    # XXX: Make sure these HTTPError exception are coming from SyncClient.
    statsd_count(request, "syncclient.status_code.%s" %
                 context.response.status_code)

    if context.response.status_code in (400, 401, 403, 404):
        # For this code we also want to log the info about the error.
        logger.info(context, exc_info=True)

    # For this specific code we do not want to log the error.
    if context.response.status_code == 304:
        response = httpexceptions.HTTPNotModified()
    elif context.response.status_code == 400:
        response = http_error(httpexceptions.HTTPBadRequest(),
                              errno=ERRORS.INVALID_PARAMETERS,
                              message=message)
    elif context.response.status_code == 401:
        response = http_error(httpexceptions.HTTPUnauthorized(),
                              errno=ERRORS.INVALID_AUTH_TOKEN,
                              message=message)
        # Forget the current user credentials.
        response.headers.extend(forget(request))
    elif context.response.status_code == 403:
        response = http_error(httpexceptions.HTTPForbidden(),
                              errno=ERRORS.FORBIDDEN,
                              message=message)
    elif context.response.status_code == 404:
        response = http_error(httpexceptions.HTTPNotFound(),
                              errno=ERRORS.INVALID_RESOURCE_ID,
                              message=message)
    elif context.response.status_code == 412:
        message = 'Resource was modified meanwhile'
        response = http_error(httpexceptions.HTTPPreconditionFailed(),
                              errno=ERRORS.MODIFIED_MEANWHILE,
                              message=message)
    else:
        # For this code we also want to log the error.
        logger.error(context, exc_info=True)
        response = service_unavailable(
            httpexceptions.HTTPServiceUnavailable(),
            request)

    request.response = response
    export_headers(context.response, request)

    return reapply_cors(request, response)
Пример #5
0
def page_not_found(request):
    """Return a JSON 404 error response."""
    config_key = 'trailing_slash_redirect_enabled'
    redirect_enabled = request.registry.settings[config_key]
    trailing_slash_redirection_enabled = asbool(redirect_enabled)

    querystring = request.url[(request.url.rindex(request.path) +
                               len(request.path)):]

    errno = ERRORS.MISSING_RESOURCE
    error_msg = "The resource you are looking for could not be found."

    if not request.path.startswith('/' + request.registry.route_prefix):
        errno = ERRORS.VERSION_NOT_AVAILABLE
        error_msg = ("The requested protocol version is not available "
                     "on this server.")
    elif trailing_slash_redirection_enabled:
        redirect = None

        if request.path.endswith('/'):
            path = request.path.rstrip('/')
            redirect = '%s%s' % (path, querystring)
        elif request.path == '/' + request.registry.route_prefix:
            # Case for /v0 -> /v0/
            redirect = '/%s/%s' % (request.registry.route_prefix, querystring)

        if redirect:
            return HTTPTemporaryRedirect(redirect)

    response = http_error(httpexceptions.HTTPNotFound(),
                          errno=errno,
                          message=error_msg)
    return response
Пример #6
0
def page_not_found(request):
    """Return a JSON 404 error response."""
    error_msg = "The resource your are looking for could not be found."
    response = http_error(httpexceptions.HTTPNotFound(),
                          errno=ERRORS.MISSING_RESOURCE,
                          message=error_msg)
    return response
Пример #7
0
    def eos_tween(request):
        eos_date = registry.settings['cliquet.eos']
        eos_url = registry.settings['cliquet.eos_url']
        eos_message = registry.settings['cliquet.eos_message']
        if eos_date:
            eos_date = dateparser.parse(eos_date)
            alert = {}
            if eos_url is not None:
                alert['url'] = eos_url

            if eos_message is not None:
                alert['message'] = eos_message

            if eos_date > datetime.datetime.now():
                alert['code'] = "soft-eol"
                response = handler(request)
            else:
                response = errors.http_error(
                    HTTPGone(),
                    errno=errors.ERRORS.SERVICE_DEPRECATED,
                    message=deprecation_msg)
                alert['code'] = "hard-eol"
            response.headers['Alert'] = utils.json.dumps(alert)
            return response
        return handler(request)
Пример #8
0
    def _raise_412_if_modified(self, record=None):
        """Raise 412 if current timestamp is superior to the one
        specified in headers.

        :raises:
            :exc:`~pyramid:pyramid.httpexceptions.HTTPPreconditionFailed`
        """
        unmodified_since = self.request.headers.get('If-Unmodified-Since')

        if unmodified_since:
            unmodified_since = int(unmodified_since)

            if record:
                current_timestamp = record[self.modified_field]
            else:
                current_timestamp = self.db.collection_timestamp(
                    **self.db_kwargs)

            if current_timestamp > unmodified_since:
                error_msg = 'Resource was modified meanwhile'
                response = http_error(HTTPPreconditionFailed(),
                                      errno=ERRORS.MODIFIED_MEANWHILE,
                                      message=error_msg)
                self._add_timestamp_header(response)
                raise response
Пример #9
0
def method_not_allowed(context, request):
    if context.content_type == 'application/json':
        return context

    response = http_error(context,
                          errno=ERRORS.METHOD_NOT_ALLOWED,
                          message="Method not allowed on this endpoint.")
    return reapply_cors(request, response)
Пример #10
0
def authorization_required(request):
    """Distinguish authentication required (``401 Unauthorized``) from
    not allowed (``403 Forbidden``).
    """
    if Authenticated not in request.effective_principals:
        error_msg = "Please authenticate yourself to use this endpoint."
        response = http_error(httpexceptions.HTTPUnauthorized(),
                              errno=ERRORS.MISSING_AUTH_TOKEN,
                              message=error_msg)
        response.headers.extend(forget(request))
        return response

    error_msg = "This user cannot access this resource."
    response = http_error(httpexceptions.HTTPForbidden(),
                          errno=ERRORS.FORBIDDEN,
                          message=error_msg)
    return response
Пример #11
0
def service_unavailable(response, request):
    if response.content_type != 'application/json':
        error_msg = "Service unavailable due to high load, please retry later."
        response = http_error(response, errno=ERRORS.BACKEND,
                              message=error_msg)

    retry_after = request.registry.settings['retry_after_seconds']
    response.headers["Retry-After"] = encode_header('%s' % retry_after)
    return reapply_cors(request, response)
Пример #12
0
def service_unavailable(context, request):

    error_msg = "Service unavailable due to high load, please retry later."
    response = http_error(httpexceptions.HTTPServiceUnavailable(),
                          errno=ERRORS.BACKEND,
                          message=error_msg)

    retry_after = request.registry.settings['cliquet.retry_after_seconds']
    response.headers["Retry-After"] = ('%s' % retry_after).encode("utf-8")
    return reapply_cors(request, response)
Пример #13
0
def post_batch(request):
    requests = request.validated['requests']
    batch_size = len(requests)

    limit = request.registry.settings['batch_max_requests']
    if limit and len(requests) > int(limit):
        error_msg = 'Number of requests is limited to %s' % limit
        request.errors.add('body', 'requests', error_msg)
        return

    if any([batch.path in req['path'] for req in requests]):
        error_msg = 'Recursive call on %s endpoint is forbidden.' % batch.path
        request.errors.add('body', 'requests', error_msg)
        return

    responses = []

    sublogger = logger.new()

    for subrequest_spec in requests:
        subrequest = build_request(request, subrequest_spec)

        sublogger.bind(path=subrequest.path,
                       method=subrequest.method)
        try:
            # Invoke subrequest without individual transaction.
            resp, subrequest = request.follow_subrequest(subrequest,
                                                         use_tweens=False)
        except httpexceptions.HTTPException as e:
            if e.content_type == 'application/json':
                resp = e
            else:
                # JSONify raw Pyramid errors.
                resp = errors.http_error(e)
        except Exception as e:
            resp = render_view_to_response(e, subrequest)
            if resp.status_code >= 500:
                raise e

        sublogger.bind(code=resp.status_code)
        sublogger.info('subrequest.summary')

        dict_resp = build_response(resp, subrequest)
        responses.append(dict_resp)

    # Rebing batch request for summary
    logger.bind(path=batch.path,
                method=request.method,
                batch_size=batch_size,
                agent=request.headers.get('User-Agent'),)

    return {
        'responses': responses
    }
Пример #14
0
def request_error(context, request):
    """Catch requests errors when issuing a request to Sync."""
    logger.error(context, exc_info=True)

    error_msg = ("Unable to reach the service. "
                 "Check your internet connection or firewall configuration.")
    response = http_error(httpexceptions.HTTPServiceUnavailable(),
                          errno=ERRORS.BACKEND,
                          message=error_msg)

    return service_unavailable(response, request)
Пример #15
0
 def test_405_can_have_custom_message(self):
     custom_405 = http_error(httpexceptions.HTTPMethodNotAllowed(),
                             errno=ERRORS.METHOD_NOT_ALLOWED,
                             message="Disabled from conf.")
     with mock.patch(
             'cliquet.tests.testapp.views.Mushroom._extract_filters',
             side_effect=custom_405):
         response = self.app.get(self.sample_url,
                                 headers=self.headers, status=405)
     self.assertFormattedError(
         response, 405, ERRORS.METHOD_NOT_ALLOWED, "Method Not Allowed",
         "Disabled from conf.")
Пример #16
0
    def _get_record_or_404(self, record_id):
        """Retrieve record from storage and raise ``404 Not found`` if missing.

        :raises: :exc:`~pyramid:pyramid.httpexceptions.HTTPNotFound` if
            the record is not found.
        """
        try:
            return self.collection.get_record(record_id)
        except storage_exceptions.RecordNotFoundError:
            response = http_error(HTTPNotFound(),
                                  errno=ERRORS.INVALID_RESOURCE_ID)
            raise response
Пример #17
0
 def test_503_can_have_custom_message(self):
     custom_503 = http_error(httpexceptions.HTTPServiceUnavailable(),
                             errno=ERRORS.BACKEND,
                             message="Unable to connect the server.")
     with mock.patch(
             'cliquet.tests.testapp.views.Mushroom._extract_filters',
             side_effect=custom_503):
         response = self.app.get(self.sample_url,
                                 headers=self.headers, status=503)
     self.assertFormattedError(
         response, 503, ERRORS.BACKEND, "Service Unavailable",
         "Unable to connect the server.")
Пример #18
0
def assert_endpoint_enabled(request, collection_name):
    """Check that endpoint is not disabled from configuration.
    """
    settings = request.registry.settings
    method = request.method.lower()
    setting_key = 'record_%s_%s_enabled' % (collection_name, method)
    enabled = settings.get(setting_key, False)
    if not enabled:
        error_msg = 'Endpoint disabled for this collection in configuration.'
        response = errors.http_error(httpexceptions.HTTPMethodNotAllowed(),
                                     errno=errors.ERRORS.METHOD_NOT_ALLOWED,
                                     message=error_msg)
        raise response
Пример #19
0
    def _raise_412_if_modified(self, record=None):
        """Raise 412 if current timestamp is superior to the one
        specified in headers.

        :raises:
            :exc:`~pyramid:pyramid.httpexceptions.HTTPPreconditionFailed`
        """
        if_match = self.request.headers.get('If-Match')
        if_none_match = self.request.headers.get('If-None-Match')

        if not if_match and not if_none_match:
            return

        if_match = decode_header(if_match) if if_match else None

        if record and if_none_match and decode_header(if_none_match) == '*':
            if record.get(self.model.deleted_field, False):
                # Tombstones should not prevent creation.
                return
            modified_since = -1  # Always raise.
        elif if_match:
            try:
                if not (if_match[0] == if_match[-1] == '"'):
                    raise ValueError()
                modified_since = int(if_match[1:-1])
            except (IndexError, ValueError):
                message = ("Invalid value for If-Match. The value should "
                           "be integer between double quotes.")
                error_details = {
                    'location': 'headers',
                    'description': message
                }
                raise_invalid(self.request, **error_details)
        else:
            # In case _raise_304_if_not_modified() did not raise.
            return

        if record:
            current_timestamp = record[self.model.modified_field]
        else:
            current_timestamp = self.model.timestamp()

        if current_timestamp > modified_since:
            error_msg = 'Resource was modified meanwhile'
            details = {'existing': record} if record else {}
            response = http_error(HTTPPreconditionFailed(),
                                  errno=ERRORS.MODIFIED_MEANWHILE,
                                  message=error_msg,
                                  details=details)
            self._add_timestamp_header(response, timestamp=current_timestamp)
            raise response
Пример #20
0
def page_not_found(request):
    """Return a JSON 404 error response."""
    if request.path.startswith('/' + request.registry.route_prefix):
        errno = ERRORS.MISSING_RESOURCE
        error_msg = "The resource your are looking for could not be found."
    else:
        errno = ERRORS.VERSION_NOT_AVAILABLE
        error_msg = ("The requested protocol version is not available "
                     "on this server.")

    response = http_error(httpexceptions.HTTPNotFound(),
                          errno=errno,
                          message=error_msg)
    return response
Пример #21
0
    def get_record(self, record_id):
        """Fetch current view related record, and raise 404 if missing.

        :raises: :class:`pyramid.httpexceptions.HTTPNotFound`
        :returns: the record from storage
        :rtype: dict
        """
        try:
            return self.db.get(record_id=record_id,
                               **self.db_kwargs)
        except storage_exceptions.RecordNotFoundError:
            response = http_error(HTTPNotFound(),
                                  errno=ERRORS.INVALID_RESOURCE_ID)
            raise response
Пример #22
0
    def test_formatted_error_are_passed_through(self):
        response = http_error(HTTPBadRequest(),
                              errno=ERRORS.INVALID_PARAMETERS,
                              message='Yop')

        with mock.patch.object(self.storage, 'create') as mocked:
            mocked.side_effect = [
                {"id": "abc", "last_modified": 43},
                {"id": "abc", "last_modified": 44},
                response
            ]
            resp = self.app.post(self.collection_url + '/records',
                                 headers=self.headers,
                                 status=400)
            self.assertEqual(resp.body, response.body)
Пример #23
0
    def test_formatted_error_are_passed_through(self):
        request = mock.MagicMock()
        request.method = "PUT"
        request.url = "http://localhost/v1/buckets/default/collections/tasks"
        request.path = "http://localhost/v1/buckets/default/collections/tasks"
        request.prefixed_userid = "fxa:abcd"
        request.registry.settings = {"readonly": False, "userid_hmac_secret": "This is no secret"}

        response = http_error(HTTPBadRequest(), errno=ERRORS.INVALID_PARAMETERS, message="Yop")

        with mock.patch("kinto.views.buckets.create_bucket"):
            with mock.patch("kinto.views.buckets.create_collection"):
                request.invoke_subrequest.side_effect = response
                resp = default_bucket(request)
                self.assertEqual(resp.body, response.body)
Пример #24
0
    def _raise_conflict(self, exception):
        """Helper to raise conflict responses.

        :param exception: the original unicity error
        :type exception: :class:`cliquet.storage.exceptions.UnicityError`
        :raises: :class:`pyramid.httpexceptions.HTTPConflict`
        """
        field = exception.field
        existing = exception.record[self.id_field]
        message = 'Conflict of field {0} on record {1}'.format(field, existing)
        response = http_error(HTTPConflict(),
                              errno=ERRORS.CONSTRAINT_VIOLATED,
                              message=message)
        response.existing = exception.record
        raise response
Пример #25
0
    def _get_record_or_404(self, record_id):
        """Retrieve record from storage and raise ``404 Not found`` if missing.

        :raises: :exc:`~pyramid:pyramid.httpexceptions.HTTPNotFound` if
            the record is not found.
        """
        if self.context and self.context.current_record:
            # Set during authorization. Save a storage hit.
            return self.context.current_record

        try:
            return self.model.get_record(record_id)
        except storage_exceptions.RecordNotFoundError:
            response = http_error(HTTPNotFound(),
                                  errno=ERRORS.INVALID_RESOURCE_ID)
            raise response
Пример #26
0
def error(context, request):
    """Catch server errors and trace them."""
    if isinstance(context, httpexceptions.Response):
        return reapply_cors(request, context)

    if isinstance(context, storage_exceptions.BackendError):
        logger.critical(context.original, exc_info=True)
        return service_unavailable(context, request)

    logger.error(context, exc_info=True)

    error_msg = "A programmatic error occured, developers have been informed."
    info = "https://github.com/mozilla-services/cliquet/issues/"
    response = http_error(httpexceptions.HTTPInternalServerError(),
                          message=error_msg,
                          info=info)

    return reapply_cors(request, response)
Пример #27
0
    def eos_tween(request):
        eos_date = registry.settings["eos"]
        eos_url = registry.settings["eos_url"]
        eos_message = registry.settings["eos_message"]
        if not eos_date:
            return handler(request)

        eos_date = dateparser.parse(eos_date)
        if eos_date > datetime.now():
            code = "soft-eol"
            request.response = handler(request)
        else:
            code = "hard-eol"
            request.response = errors.http_error(
                HTTPGone(), errno=errors.ERRORS.SERVICE_DEPRECATED, message=deprecation_msg
            )

        errors.send_alert(request, eos_message, url=eos_url, code=code)
        return request.response
Пример #28
0
    def _raise_conflict(self, exception):
        """Helper to raise conflict responses.

        :param exception: the original unicity error
        :type exception: :class:`cliquet.storage.exceptions.UnicityError`
        :raises: :exc:`~pyramid:pyramid.httpexceptions.HTTPConflict`
        """
        field = exception.field
        record_id = exception.record[self.model.id_field]
        message = 'Conflict of field %s on record %s' % (field, record_id)
        details = {
            "field": field,
            "existing": exception.record,
        }
        response = http_error(HTTPConflict(),
                              errno=ERRORS.CONSTRAINT_VIOLATED,
                              message=message,
                              details=details)
        raise response
Пример #29
0
    def _raise_412_if_modified(self, record=None):
        """Raise 412 if current timestamp is superior to the one
        specified in headers.

        :raises:
            :exc:`~pyramid:pyramid.httpexceptions.HTTPPreconditionFailed`
        """
        if_match = self.request.headers.get('If-Match')
        if_none_match = self.request.headers.get('If-None-Match')

        if not if_match and not if_none_match:
            return

        if_match = if_match.decode('utf-8') if if_match else None

        if if_none_match and if_none_match.decode('utf-8') == '*':
            modified_since = -1  # Always raise.
        elif if_match:
            try:
                assert if_match[0] == if_match[-1] == '"'
                modified_since = int(if_match[1:-1])
            except (IndexError, AssertionError, ValueError):
                error_details = {
                    'location': 'headers',
                    'description': "Invalid value for If-Match"
                }
                raise_invalid(self.request, **error_details)
        else:
            # In case _raise_304_if_not_modified() did not raise.
            return

        if record:
            current_timestamp = record[self.collection.modified_field]
        else:
            current_timestamp = self.collection.timestamp()

        if current_timestamp > modified_since:
            error_msg = 'Resource was modified meanwhile'
            response = http_error(HTTPPreconditionFailed(),
                                  errno=ERRORS.MODIFIED_MEANWHILE,
                                  message=error_msg)
            self._add_timestamp_header(response, timestamp=current_timestamp)
            raise response
Пример #30
0
def error(context, request):
    """Catch server errors and trace them."""
    if isinstance(context, httpexceptions.Response):
        return reapply_cors(request, context)

    if isinstance(context, storage_exceptions.BackendError):
        logger.critical(context.original, exc_info=True)
        response = httpexceptions.HTTPServiceUnavailable()
        return service_unavailable(response, request)

    logger.error(context, exc_info=True)

    error_msg = "A programmatic error occured, developers have been informed."
    info = request.registry.settings['error_info_link']
    response = http_error(httpexceptions.HTTPInternalServerError(),
                          message=error_msg,
                          info=info)

    return reapply_cors(request, response)