Feed error messages through a cors wrapper so that people on other domains can see what's happening.

This commit is contained in:
jakedt 2014-03-17 16:57:35 -04:00
parent 4673f40dd2
commit 3b3d71bfd7
18 changed files with 162 additions and 129 deletions

View file

@ -1,9 +1,10 @@
import logging
import json
from flask import Blueprint, request
from flask import Blueprint, request, make_response, jsonify
from flask.ext.restful import Resource, abort, Api, reqparse
from flask.ext.restful.utils.cors import crossdomain
from werkzeug.exceptions import HTTPException
from calendar import timegm
from email.utils import formatdate
from functools import partial, wraps
@ -27,6 +28,57 @@ api.decorators = [process_oauth,
crossdomain(origin='*', headers=['Authorization', 'Content-Type'])]
class ApiException(Exception):
def __init__(self, error_type, status_code, error_description, payload=None):
Exception.__init__(self)
self.error_description = error_description
self.status_code = status_code
self.payload = payload
self.error_type = error_type
def to_dict(self):
rv = dict(self.payload or ())
if self.error_description is not None:
rv['error_description'] = self.error_description
rv['error_type'] = self.error_type
return rv
invalid_request = partial(ApiException, 'invalid_request', 400)
class InvalidRequest(ApiException):
def __init__(self, error_description, payload=None):
ApiException.__init__(self, 'invalid_request', 400, error_description, payload)
class InvalidToken(ApiException):
def __init__(self, error_description, payload=None):
ApiException.__init__(self, 'invalid_token', 401, error_description, payload)
class Unauthorized(ApiException):
def __init__(self, payload=None):
ApiException.__init__(self, 'insufficient_scope', 403, 'Unauthorized', payload)
class NotFound(ApiException):
def __init__(self, payload=None):
ApiException.__init__(self, None, 404, 'Not Found', payload)
@api_bp.app_errorhandler(ApiException)
@crossdomain(origin='*', headers=['Authorization', 'Content-Type'])
def handle_api_error(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
if error.error_type is not None:
response.headers['WWW-Authenticate'] = ('Bearer error="%s" error_description="%s"' %
(error.error_type, error.error_description))
return response
def resource(*urls, **kwargs):
def wrapper(api_resource):
api.add_resource(api_resource, *urls, **kwargs)
@ -84,7 +136,7 @@ def parse_args(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
if '__api_query_params' not in dir(func):
abort(400)
abort(500)
parser = reqparse.RequestParser()
for arg_spec in func.__api_query_params:
@ -124,7 +176,7 @@ def require_repo_permission(permission_class, scope, allow_public=False):
(allow_public and
model.repository_is_public(namespace, repository))):
return func(self, namespace, repository, *args, **kwargs)
abort(403)
raise Unauthorized()
return wrapped
return wrapper
@ -154,17 +206,14 @@ def validate_json_request(schema_name):
validate(request.get_json(), schema)
return func(self, *args, **kwargs)
except ValidationError as ex:
abort(400, message=ex.message)
InvalidRequest(ex.message)
return wrapped
return wrapper
def request_error(exception=None, **kwargs):
data = kwargs.copy()
if exception:
data['message'] = exception.message
return json.dumps(data), 400
raise InvalidRequest(exception.message, data)
def log_action(kind, user_or_orgname, metadata={}, repo=None):