Example #1
0
    def connect(self, readonly=False, force_commit=False):
        """
        Pulls a connection from the pool when context is entered and
        returns it when context is exited.

        A COMMIT is performed on the current transaction if everything went
        well. Otherwise transaction is ROLLBACK, and everything cleaned up.
        """
        with_transaction = not readonly and self.commit_manually
        session = None
        try:
            # Pull connection from pool.
            session = self.session_factory()
            # Start context
            yield session
            if not readonly and not self.commit_manually:
                # Mark session as dirty.
                self.invalidate(session)
            # Success
            if with_transaction:
                session.commit()
            elif force_commit:
                # Commit like would do a succesful request.
                zope_transaction.commit()

        except sqlalchemy.exc.SQLAlchemyError as e:
            logger.error(e)
            if session and with_transaction:
                session.rollback()
            raise exceptions.BackendError(original=e)

        finally:
            if session and self.commit_manually:
                # Give back to pool if commit done manually.
                session.close()
Example #2
0
def fxa_oauth_token(request):
    """Return OAuth token from authorization code.
    """
    state = request.validated['state']
    code = request.validated['code']

    # Require on-going session
    stored_redirect = request.cache.get(state)

    # Make sure we cannot try twice with the same code
    request.registry.cache.delete(state)
    if not stored_redirect:
        return authorization_required(request)

    # Trade the OAuth code for a longer-lived token
    auth_client = OAuthClient(server_url=fxa_conf(request, 'oauth_uri'),
                              client_id=fxa_conf(request, 'client_id'),
                              client_secret=fxa_conf(request, 'client_secret'))
    try:
        token = auth_client.trade_code(code)
    except fxa_errors.OutOfProtocolError:
        raise httpexceptions.HTTPServiceUnavailable()
    except fxa_errors.InProtocolError as error:
        logger.error(error)
        error_details = {
            'name': 'code',
            'location': 'querystring',
            'description': 'Firefox Account code validation failed.'
        }
        errors.raise_invalid(request, **error_details)

    return httpexceptions.HTTPFound(location='%s%s' % (stored_redirect, token))
Example #3
0
    def connect(self, readonly=False):
        """Connect to the database and instantiates a cursor.
        At exiting the context manager, a COMMIT is performed on the current
        transaction if everything went well. Otherwise transaction is ROLLBACK,
        and everything cleaned up.

        If the database could not be be reached a 503 error is raised.
        """
        conn = None
        cursor = None
        try:
            conn = self.pool.getconn()
            conn.autocommit = readonly
            options = dict(cursor_factory=psycopg2.extras.DictCursor)
            cursor = conn.cursor(**options)
            # Start context
            yield cursor
            # End context
            if not readonly:
                conn.commit()
        except psycopg2.Error as e:
            if cursor:
                logger.debug(cursor.query)
            logger.error(e)
            if conn and not conn.closed:
                conn.rollback()
            raise exceptions.BackendError(original=e)
        finally:
            if cursor:
                cursor.close()
            if conn and not conn.closed:
                self.pool.putconn(conn, close=self._always_close)
Example #4
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)
Example #5
0
    def connect(self, readonly=False, force_commit=False):
        """
        Pulls a connection from the pool when context is entered and
        returns it when context is exited.

        A COMMIT is performed on the current transaction if everything went
        well. Otherwise transaction is ROLLBACK, and everything cleaned up.
        """
        with_transaction = not readonly and self.commit_manually
        session = None
        try:
            # Pull connection from pool.
            session = self.session_factory()
            # Start context
            yield session
            if not readonly and not self.commit_manually:
                # Mark session as dirty.
                self.invalidate(session)
            # Success
            if with_transaction:
                session.commit()
            elif force_commit:
                # Commit like would do a succesful request.
                zope_transaction.commit()

        except sqlalchemy.exc.SQLAlchemyError as e:
            logger.error(e)
            if session and with_transaction:
                session.rollback()
            raise exceptions.BackendError(original=e)

        finally:
            if session and self.commit_manually:
                # Give back to pool if commit done manually.
                session.close()
Example #6
0
 def __call__(self, event):
     try:
         payload = json.dumps(event.payload)
     except TypeError:
         logger.error("Unable to dump the payload", exc_info=True)
         return
     try:
         self._client.lpush(self.listname, payload)
     except Exception:
         logger.error("Unable to send the payload to Redis", exc_info=True)
Example #7
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)
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)
Example #9
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
    }
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)
Example #11
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)
Example #12
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)
Example #13
0
    def _notify_event(self, result, action, old):
        """
        Emit a :class:`cliquet.event.ResourceChanged` event with the
        impacted records.

        # XXX: this should be plugged with the transaction system.
        When plugged with pyramid_tm, this method will have to be rewritten
        to just populate the ``bound_data`` attribute of ``self.request``.
        (since it is shared among batch requests).
        """
        # When plugged with the transaction system, impacted records will
        # always be a list. Therefore, here put single operations into a
        # list.
        if not isinstance(result, list):
            result = [result]

        if action == ACTIONS.READ:
            event_cls = ResourceRead
        else:
            event_cls = ResourceChanged

        if action == ACTIONS.READ:
            impacted = result
        elif action == ACTIONS.CREATE:
            impacted = [{'new': r} for r in result]
        elif action == ACTIONS.DELETE:
            impacted = [{'old': r} for r in result]
        elif action == ACTIONS.UPDATE:
            # XXX `old` is "always" the same, since currently, we can only
            # notify one update at a time.
            # This code will change when plugged with transactions (and batch).
            impacted = [{'new': r, 'old': old} for r in result]

        try:
            event = event_cls(action, self, impacted, self.request)
            self.request.registry.notify(event)
        except Exception:
            logger.error("Unable to notify", exc_info=True)