Пример #1
0
    async def get(self, *args, **kwargs):
        if self.get_argument('full', 'false') == 'true':
            query_args = self._get_query_kwargs(kwargs)
            project, facts, links, urls = await asyncio.gather(
                self.postgres_execute(
                    self.GET_FULL_SQL, query_args, 'get-{}'.format(self.NAME)),
                self.postgres_execute(
                    self.GET_FACTS_SQL, query_args, 'get-project-facts'),
                self.postgres_execute(
                    self.GET_LINKS_SQL, query_args, 'get-project-links'),
                self.postgres_execute(
                    self.GET_URLS_SQL, query_args, 'get-project-urls'))

            if not project.row_count or not project.row:
                raise problemdetails.Problem(
                    status_code=404, title='Item not found')

            output = project.row
            output.update({
                'facts': facts.rows,
                'links': links.rows,
                'urls': {row['environment']: row['url'] for row in urls.rows}
            })
            self.send_response(output)
        else:
            await self._get(kwargs)
Пример #2
0
    def get(self):
        sentinel = object()  # safely detect missing query params

        status = int(self.get_query_argument('status', default='200'))
        raise_error = self.get_query_argument('raise_error', default=sentinel)

        if raise_error is not sentinel:
            query_args = [
                arg for arg in self.request.query_arguments.keys()
                if arg not in ('raise_error', 'status')
            ]
            kwargs = {}
            for name in query_args:
                kwargs[name] = self.get_query_argument(name)
                if kwargs[name].lower() == 'none':
                    kwargs[name] = None
                elif kwargs[name].startswith(('{', '[')):
                    kwargs[name] = json.loads(kwargs[name])
            raise problemdetails.Problem(status_code=status, **kwargs)
        else:
            kwargs = {}
            for name in ('detail', 'instance', 'title', 'type'):
                value = self.get_query_argument(name, sentinel)
                if value is not sentinel:
                    if value.lower() == 'none':
                        value = None
                    kwargs[name] = value
            if status not in httputil.responses:
                kwargs['reason'] = 'Abnormal Status'
            self.send_error(status, **kwargs)
Пример #3
0
    async def _post(self, kwargs) -> None:
        values = self.get_request_body()

        # Handle compound keys for child object CRUD
        if isinstance(self.ID_KEY, list):
            for key in self.ID_KEY:
                if key not in values and key in kwargs:
                    values[key] = kwargs[key]
        elif self.ID_KEY not in values and self.ID_KEY in kwargs:
            values[self.ID_KEY] = kwargs[self.ID_KEY]

        # Set defaults of None for all fields in insert
        for name in self.FIELDS:
            if name not in values:
                values[name] = self.DEFAULTS.get(name)

        values['username'] = self._current_user.username
        result = await self.postgres_execute(
            self.POST_SQL, values, 'post-{}'.format(self.NAME))
        if not result.row_count:
            self.logger.debug('No rows returned')
            raise problemdetails.Problem(
                status_code=500, title='Failed to create record')

            # Return the record as if it were a GET
        await self._get(self._get_query_kwargs(result.row))
Пример #4
0
    async def _patch(self, kwargs):
        patch_value = self.get_request_body()

        result = await self.postgres_execute(
            self.GET_SQL, self._get_query_kwargs(kwargs),
            'get-{}'.format(self.NAME))
        if not result.row_count:
            raise problemdetails.Problem(
                status_code=404, title='Item not found')

        original = dict(result.row)
        for key in {'created_at', 'created_by',
                    'last_modified_at', 'last_modified_by'}:
            del original[key]

        for key, value in original.items():
            if isinstance(value, uuid.UUID):
                original[key] = str(value)

        # Apply the patch to the current value
        patch = jsonpatch.JsonPatch(patch_value)
        updated = patch.apply(original)

        # Bail early if there are no changes
        if not {k: original[k] for k in original
                if k in updated and original[k] != updated[k]}:
            self._add_self_link(self.request.path)
            self._add_link_header()
            return self.set_status(304)

        if isinstance(self.ID_KEY, list):
            for key in self.ID_KEY:
                updated['current_{}'.format(key)] = kwargs[key]
        else:
            updated['current_{}'.format(self.ID_KEY)] = kwargs[self.ID_KEY]
        updated['username'] = self._current_user.username

        result = await self.postgres_execute(
            self.PATCH_SQL, updated,
            'patch-{}'.format(self.NAME))
        if not result.row_count:
            raise problemdetails.Problem(
                status_code=500, title='Failed to update record')

        # Send the new record as a response
        await self._get(self._get_query_kwargs(updated))
Пример #5
0
 async def _delete(self, kwargs):
     result = await self.postgres_execute(
         self.DELETE_SQL, self._get_query_kwargs(kwargs),
         'delete-{}'.format(self.NAME))
     if not result.row_count:
         raise problemdetails.Problem(
             status_code=404, title='Item not found')
     self.set_status(204, reason='Item Deleted')
Пример #6
0
 async def get(self, *args, **kwargs):
     if self.GET_SQL is None:
         self.logger.debug('GET_SQL not defined')
         raise problemdetails.Problem(
             status_code=405, title='Not Implemented')
     if self._respond_with_html:
         return self.render('index.html')
     await self._get(kwargs)
Пример #7
0
 def get(self):
     age = self._extract_age()
     color = self._extract_color()
     if self.errors:
         raise problemdetails.Problem(
             **{
                 'status_code': 400,
                 'type': 'https://example.net/validation-error',
                 'invalid-params': self.errors,
             })
     self.write({'age': age, 'color': color})
Пример #8
0
 def wrapped(self, *args, **kwargs):
     """Inner-wrapping of the decorator that performs the logic"""
     if not self._current_user or \
             not self._current_user.has_permission(permission):
         if self._respond_with_html:
             return self.render('index.html')
         LOGGER.info('%r does not have the "%s" permission',
                     self._current_user, permission)
         raise problemdetails.Problem(
             status_code=403, title='Unauthorized')
     return f(self, *args, **kwargs)
Пример #9
0
 async def _get(self, kwargs):
     result = await self.postgres_execute(
         self.GET_SQL, self._get_query_kwargs(kwargs),
         'get-{}'.format(self.NAME))
     if not result.row_count or not result.row:
         raise problemdetails.Problem(
             status_code=404, title='Item not found')
     for key, value in result.row.items():
         if isinstance(value, uuid.UUID):
             result.row[key] = str(value)
     self.send_response(result.row)
Пример #10
0
    def _postgres_connection_check(self):
        """Ensures Postgres is connected, exiting the request in error if not

        :raises: problemdetails.Problem
        :raises: web.HTTPError

        """
        if not self.application.postgres_is_connected:
            if problemdetails:
                raise problemdetails.Problem(status_code=503,
                                             title='Database Connection Error')
            raise web.HTTPError(503, reason='Database Connection Error')
Пример #11
0
 async def prepare(self) -> None:
     await super().prepare()
     try:
         self.application.validate_request(self.request)
     except DeserializeError as err:
         self.logger.warning('Request failed to deserialize: %s', err)
         raise problemdetails.Problem(
             status_code=400, title='Bad Request', detail=str(err))
     except InvalidSecurity as err:
         self.logger.debug('Invalid OpenAPI spec security: %s', err)
         raise problemdetails.Problem(
             status_code=500, title='OpenAPI security error',
             detail=str(err))
     except OperationNotFound as err:
         raise problemdetails.Problem(
             status_code=405, title='Method Not Allowed', detail=str(err))
     except InvalidContentType as err:
         raise problemdetails.Problem(
             status_code=415, title='Unsupported Media Type',
             detail=str(err))
     except PathNotFound as err:
         self.logger.error('OpenAPI Spec Error: %s', err)
         raise problemdetails.Problem(
             status_code=500, title='OpenAPI Spec Error', detail=str(err))
     except ValidateError as err:
         self.logger.warning('Request failed to validate: %s', err)
         raise problemdetails.Problem(
             status_code=400, title='Bad Request',
             detail='The request did not validate',
             errors=[str(e).split('\n')[0] for e in err.schema_errors])
Пример #12
0
    def get(self, record_id):
        record = self.settings['database'].fetch(record_id)
        if record is None:
            raise problemdetails.Problem(
                status_code=404,
                type='/errors#error-customer-does-not-exist',
                title='Customer {0} does not exist'.format(record_id),
                instance=self.reverse_url('retrieve', record_id),
            )

        self.set_header('Content-Type', 'application/json')
        self.set_status(200)
        self.write(self.settings['json-encoder'].encode(record))
Пример #13
0
 def verify_funds(self, account, price):
     balance = self.get_balance(account)
     if price > balance:
         raise problemdetails.Problem(
             status_code=403,
             title='You do not have enough credit.',
             detail=(f'Your current balance is {balance}, but that '
                     f'costs {price}'),
             instance=self.reverse_url('account-handler', account),
             balance=balance,
             accounts=self.lookup_accounts(account),
             type='https://example.com/probs/out-of-credit',
         )
Пример #14
0
    def post(self):
        content_type, params = cgi.parse_header(
            self.request.headers.get('Content-Type', 'application/json'))
        if content_type != 'application/json':
            raise problemdetails.Problem(
                status_code=415,
                title='Content type is not understood',
                detail='Cannot decode {0}, try application/json'.format(
                    content_type),
            )

        try:
            body = json.loads(self.request.body.decode('utf-8'))
        except (TypeError, ValueError, UnicodeDecodeError) as error:
            raise problemdetails.Problem(
                status_code=400,
                title='Failed to decode request',
                detail=str(error),
                failure=error,
            )

        openapi = self.settings['openapi']
        validator = jsonschema.Draft7Validator(
            schema=openapi['components']['schemas']['CustomerDetails'])
        all_errors = list(validator.iter_errors(body))
        most_important = jsonschema.exceptions.best_match(all_errors)
        if most_important is not None:
            raise problemdetails.Problem(
                status_code=422,
                type='/errors#jsonschema-failure',
                title='Failed to process request',
                detail=most_important.message,
                failure=all_errors,
            )

        record_id = self.settings['database'].insert(body)
        self.redirect(self.reverse_url('retrieve', record_id), status=303)
Пример #15
0
    def get(self):
        logger = logging.getLogger('HttpBinHandler')
        url = 'http://httpbin.org/status/{0}'.format(
            self.get_query_argument('status', '500'))
        logger.info('retrieving %s', url)

        client = httpclient.AsyncHTTPClient()
        try:
            response = yield client.fetch(url)
            self.add_header('Content-Type', 'application/json')
            self.write(response.body)
        except httpclient.HTTPError as error:
            raise problemdetails.Problem(status_code=error.code,
                                         httpbin_headers=dict(
                                             error.response.headers.items()))
Пример #16
0
    def on_postgres_error(self, metric_name: str,
                          exc: Exception) -> typing.Optional[Exception]:
        """Invoked when an error occurs when executing a query

        If `tornado-problem-details` is available,
        :exc:`problemdetails.Problem` will be raised instead of
        :exc:`tornado.web.HTTPError`.

        Override for different error handling behaviors.

        Return an exception if you would like for it to be raised, or swallow
        it here.

        """
        LOGGER.error('%s in %s for %s (%s)', exc.__class__.__name__,
                     self.__class__.__name__, metric_name,
                     str(exc).split('\n')[0])
        if isinstance(exc, ConnectionException):
            if problemdetails:
                raise problemdetails.Problem(status_code=503,
                                             title='Database Connection Error')
            raise web.HTTPError(503, reason='Database Connection Error')
        elif isinstance(exc, asyncio.TimeoutError):
            if problemdetails:
                raise problemdetails.Problem(status_code=500,
                                             title='Query Timeout')
            raise web.HTTPError(500, reason='Query Timeout')
        elif isinstance(exc, errors.ForeignKeyViolation):
            if problemdetails:
                raise problemdetails.Problem(status_code=409,
                                             title='Foreign Key Violation')
            raise web.HTTPError(409, reason='Foreign Key Violation')
        elif isinstance(exc, errors.UniqueViolation):
            if problemdetails:
                raise problemdetails.Problem(status_code=409,
                                             title='Unique Violation')
            raise web.HTTPError(409, reason='Unique Violation')
        elif isinstance(exc, psycopg2.OperationalError):
            if problemdetails:
                raise problemdetails.Problem(status_code=503,
                                             title='Database Error')
            raise web.HTTPError(503, reason='Database Error')
        elif isinstance(exc, psycopg2.Error):
            if problemdetails:
                raise problemdetails.Problem(status_code=500,
                                             title='Database Error')
            raise web.HTTPError(500, reason='Database Error')
        return exc
Пример #17
0
 async def delete(self, *args, **kwargs):
     if self.DELETE_SQL is None:
         self.logger.debug('DELETE_SQL not defined')
         raise problemdetails.Problem(
             status_code=405, title='Not Implemented')
     await self._delete(kwargs)
Пример #18
0
 async def post(self, *args, **kwargs):
     if self.POST_SQL is None:
         self.logger.debug('POST_SQL not defined')
         raise problemdetails.Problem(
             status_code=405, title='Not Implemented')
     await self._post(kwargs)