From 3b3d71bfd723ae8cd7d3b660d1e17cd7cf0d4bda Mon Sep 17 00:00:00 2001 From: jakedt Date: Mon, 17 Mar 2014 16:57:35 -0400 Subject: [PATCH] Feed error messages through a cors wrapper so that people on other domains can see what's happening. --- endpoints/api/__init__.py | 65 ++++++++++++++++++++++++++++++----- endpoints/api/billing.py | 18 +++++----- endpoints/api/build.py | 7 ++-- endpoints/api/image.py | 9 +++-- endpoints/api/logs.py | 7 ++-- endpoints/api/organization.py | 27 +++++++-------- endpoints/api/permission.py | 4 +-- endpoints/api/prototype.py | 29 ++++++++-------- endpoints/api/repository.py | 11 +++--- endpoints/api/repotoken.py | 5 ++- endpoints/api/robot.py | 20 +++++++---- endpoints/api/subscribe.py | 7 ++-- endpoints/api/tag.py | 6 ++-- endpoints/api/team.py | 19 +++++----- endpoints/api/trigger.py | 29 +++++++--------- endpoints/api/user.py | 17 +++++---- endpoints/api/webhook.py | 5 ++- endpoints/common.py | 6 ---- 18 files changed, 162 insertions(+), 129 deletions(-) diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index 5af49f6c5..35607b6af 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -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): diff --git a/endpoints/api/billing.py b/endpoints/api/billing.py index 8bdeba564..ec244e7fa 100644 --- a/endpoints/api/billing.py +++ b/endpoints/api/billing.py @@ -1,11 +1,9 @@ -import logging import stripe from flask import request -from flask.ext.restful import abort from endpoints.api import (resource, nickname, ApiResource, validate_json_request, log_action, - related_user_resource, internal_only) + related_user_resource, internal_only, Unauthorized, NotFound) from endpoints.api.subscribe import subscribe, subscription_view from auth.permissions import AdministerOrganizationPermission from auth.auth_context import get_authenticated_user @@ -158,7 +156,7 @@ class OrganizationCard(ApiResource): organization = model.get_organization(orgname) return get_card(organization) - abort(403) + raise Unauthorized() @nickname('setOrgCard') @validate_json_request('OrgCard') @@ -172,7 +170,7 @@ class OrganizationCard(ApiResource): log_action('account_change_cc', orgname) return response - abort(403) + raise Unauthorized() @resource('/v1/user/plan') @@ -266,7 +264,7 @@ class OrganizationPlan(ApiResource): organization = model.get_organization(orgname) return subscribe(organization, plan, token, True) # Business plan required - abort(403) + raise Unauthorized() @nickname('getOrgSubscription') def get(self, orgname): @@ -286,7 +284,7 @@ class OrganizationPlan(ApiResource): 'usedPrivateRepos': private_repos, } - abort(403) + raise Unauthorized() @resource('/v1/user/invoices') @@ -297,7 +295,7 @@ class UserInvoiceList(ApiResource): """ List the invoices for the current user. """ user = get_authenticated_user() if not user.stripe_id: - abort(404) + raise NotFound() return get_invoices(user.stripe_id) @@ -313,8 +311,8 @@ class OrgnaizationInvoiceList(ApiResource): if permission.can(): organization = model.get_organization(orgname) if not organization.stripe_id: - abort(404) + raise NotFound() return get_invoices(organization.stripe_id) - abort(403) \ No newline at end of file + raise Unauthorized() \ No newline at end of file diff --git a/endpoints/api/build.py b/endpoints/api/build.py index 6f0095f3e..4e779918e 100644 --- a/endpoints/api/build.py +++ b/endpoints/api/build.py @@ -2,12 +2,11 @@ import logging import json from flask import request -from flask.ext.restful import abort from app import app from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource, require_repo_read, require_repo_write, validate_json_request, - ApiResource, internal_only, format_date, api) + ApiResource, internal_only, format_date, api, Unauthorized, NotFound) from endpoints.common import start_build from endpoints.trigger import BuildTrigger from data import model @@ -111,7 +110,7 @@ class RepositoryBuildList(RepositoryParamResource): if associated_repository: if not ModifyRepositoryPermission(associated_repository.namespace, associated_repository.name): - abort(403) + raise Unauthorized() # Start the build. repo = model.get_repository(namespace, repository) @@ -137,7 +136,7 @@ class RepositoryBuildStatus(RepositoryParamResource): """ Return the status for the builds specified by the build uuids. """ build = model.get_repository_build(namespace, repository, build_uuid) if not build: - abort(404) + raise NotFound() can_write = ModifyRepositoryPermission(namespace, repository).can() return build_status_view(build, can_write) diff --git a/endpoints/api/image.py b/endpoints/api/image.py index 76bed243f..01f32a8bd 100644 --- a/endpoints/api/image.py +++ b/endpoints/api/image.py @@ -1,11 +1,10 @@ import json from collections import defaultdict -from flask.ext.restful import abort from app import app from endpoints.api import (resource, nickname, require_repo_read, RepositoryParamResource, - format_date) + format_date, NotFound) from data import model from util.cache import cache_control_flask_restful @@ -64,7 +63,7 @@ class RepositoryImage(RepositoryParamResource): """ Get the information available for the specified image. """ image = model.get_repo_image(namespace, repository, image_id) if not image: - abort(404) + raise NotFound() return image_view(image) @@ -81,7 +80,7 @@ class RepositoryImageChanges(RepositoryParamResource): image = model.get_repo_image(namespace, repository, image_id) if not image: - abort(404) + raise NotFound() uuid = image.storage and image.storage.uuid diffs_path = store.image_file_diffs_path(namespace, repository, image_id, uuid) @@ -90,4 +89,4 @@ class RepositoryImageChanges(RepositoryParamResource): response_json = json.loads(store.get_content(diffs_path)) return response_json except IOError: - abort(404) + raise NotFound() diff --git a/endpoints/api/logs.py b/endpoints/api/logs.py index eadfbab0d..aef308bfb 100644 --- a/endpoints/api/logs.py +++ b/endpoints/api/logs.py @@ -1,11 +1,10 @@ import json from datetime import datetime, timedelta -from flask.ext.restful import abort from endpoints.api import (resource, nickname, ApiResource, query_param, parse_args, RepositoryParamResource, require_repo_admin, related_user_resource, - format_date) + format_date, Unauthorized, NotFound) from auth.permissions import AdministerOrganizationPermission, AdministerOrganizationPermission from auth.auth_context import get_authenticated_user from data import model @@ -75,7 +74,7 @@ class RepositoryLogs(RepositoryParamResource): """ List the logs for the specified repository. """ repo = model.get_repository(namespace, repository) if not repo: - abort(404) + raise NotFound() start_time = args['starttime'] end_time = args['endtime'] @@ -119,4 +118,4 @@ class OrgLogs(ApiResource): return get_logs(orgname, start_time, end_time, performer_name=performer_name) - abort(403) \ No newline at end of file + raise Unauthorized() \ No newline at end of file diff --git a/endpoints/api/organization.py b/endpoints/api/organization.py index 2f521243f..fe886410f 100644 --- a/endpoints/api/organization.py +++ b/endpoints/api/organization.py @@ -2,10 +2,9 @@ import logging import stripe from flask import request -from flask.ext.restful import abort from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error, - related_user_resource, internal_only) + related_user_resource, internal_only, Unauthorized, NotFound) from endpoints.api.team import team_view from endpoints.api.user import User, PrivateRepositories from auth.permissions import (AdministerOrganizationPermission, OrganizationMemberPermission, @@ -83,13 +82,13 @@ class OrganizationList(ApiResource): if existing: msg = 'A user or organization with this name already exists' - return request_error(message=msg) + raise request_error(message=msg) try: model.create_organization(org_data['name'], org_data['email'], get_authenticated_user()) return 'Created', 201 except model.DataModelException as ex: - return request_error(exception=ex) + raise request_error(exception=ex) @resource('/v1/organization/') @@ -121,12 +120,12 @@ class Organization(ApiResource): try: org = model.get_organization(orgname) except model.InvalidOrganizationException: - abort(404) + raise NotFound() teams = model.get_teams_within_org(org) return org_view(org, teams) - abort(403) + raise Unauthorized() @nickname('changeOrganizationDetails') @validate_json_request('UpdateOrg') @@ -135,7 +134,7 @@ class Organization(ApiResource): try: org = model.get_organization(orgname) except model.InvalidOrganizationException: - abort(404) + raise NotFound() org_data = request.get_json() if 'invoice_email' in org_data: @@ -145,7 +144,7 @@ class Organization(ApiResource): if 'email' in org_data and org_data['email'] != org.email: new_email = org_data['email'] if model.find_user_by_email(new_email): - return request_error(message='E-mail address already used') + raise request_error(message='E-mail address already used') logger.debug('Changing email address for organization: %s', org.username) model.update_email(org, new_email) @@ -185,7 +184,7 @@ class OrgPrivateRepositories(ApiResource): return data - abort(403) + raise Unauthorized() @resource('/v1/organization//members') @@ -199,7 +198,7 @@ class OrgnaizationMemberList(ApiResource): try: org = model.get_organization(orgname) except model.InvalidOrganizationException: - abort(404) + raise NotFound() # Loop to create the members dictionary. Note that the members collection # will return an entry for *every team* a member is on, so we will have @@ -217,7 +216,7 @@ class OrgnaizationMemberList(ApiResource): return {'members': members_dict} - abort(403) + raise Unauthorized() @resource('/v1/organization//members/') @@ -231,7 +230,7 @@ class OrganizationMember(ApiResource): try: org = model.get_organization(orgname) except model.InvalidOrganizationException: - abort(404) + raise NotFound() member_dict = None member_teams = model.get_organization_members_with_teams(org, membername=membername) @@ -245,8 +244,8 @@ class OrganizationMember(ApiResource): member_dict['teams'].append(member.team.name) if not member_dict: - abort(404) + raise NotFound() return {'member': member_dict} - abort(403) \ No newline at end of file + raise Unauthorized() \ No newline at end of file diff --git a/endpoints/api/permission.py b/endpoints/api/permission.py index dd64b81eb..ba9462fb5 100644 --- a/endpoints/api/permission.py +++ b/endpoints/api/permission.py @@ -145,7 +145,7 @@ class RepositoryUserPermission(RepositoryParamResource): # This repository is not part of an organization pass except model.DataModelException as ex: - return request_error(exception=ex) + raise request_error(exception=ex) log_action('change_repo_permission', namespace, {'username': username, 'repo': repository, @@ -161,7 +161,7 @@ class RepositoryUserPermission(RepositoryParamResource): try: model.delete_user_permission(username, namespace, repository) except model.DataModelException as ex: - return request_error(exception=ex) + raise request_error(exception=ex) log_action('delete_repo_permission', namespace, {'username': username, 'repo': repository}, diff --git a/endpoints/api/prototype.py b/endpoints/api/prototype.py index 2a3dc829a..ef46157fa 100644 --- a/endpoints/api/prototype.py +++ b/endpoints/api/prototype.py @@ -1,8 +1,7 @@ from flask import request -from flask.ext.restful import abort from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error, - log_action) + log_action, Unauthorized, NotFound) from auth.permissions import AdministerOrganizationPermission from auth.auth_context import get_authenticated_user from data import model @@ -123,13 +122,13 @@ class PermissionPrototypeList(ApiResource): try: org = model.get_organization(orgname) except model.InvalidOrganizationException: - abort(404) + raise NotFound() permissions = model.get_prototype_permissions(org) org_members = model.get_organization_member_set(orgname) return {'prototypes': [prototype_view(p, org_members) for p in permissions]} - abort(403) + raise Unauthorized() @nickname('createOrganizationPrototypePermission') @validate_json_request('NewPrototype') @@ -140,7 +139,7 @@ class PermissionPrototypeList(ApiResource): try: org = model.get_organization(orgname) except model.InvalidOrganizationException: - abort(404) + raise NotFound() details = request.get_json() activating_username = None @@ -162,10 +161,10 @@ class PermissionPrototypeList(ApiResource): if delegate_teamname else None) if activating_username and not activating_user: - return request_error(message='Unknown activating user') + raise request_error(message='Unknown activating user') if not delegate_user and not delegate_team: - return request_error(message='Missing delegate user or team') + raise request_error(message='Missing delegate user or team') role_name = details['role'] @@ -175,7 +174,7 @@ class PermissionPrototypeList(ApiResource): org_members = model.get_organization_member_set(orgname) return prototype_view(prototype, org_members) - abort(403) + raise Unauthorized() @resource('/v1/organization//prototypes/') @@ -211,17 +210,17 @@ class PermissionPrototype(ApiResource): try: org = model.get_organization(orgname) except model.InvalidOrganizationException: - abort(404) + raise NotFound() prototype = model.delete_prototype_permission(org, prototypeid) if not prototype: - abort(404) + raise NotFound() log_prototype_action('delete_prototype_permission', orgname, prototype) return 'Deleted', 204 - abort(403) + raise Unauthorized() @nickname('updateOrganizationPrototypePermission') @validate_json_request('PrototypeUpdate') @@ -232,21 +231,21 @@ class PermissionPrototype(ApiResource): try: org = model.get_organization(orgname) except model.InvalidOrganizationException: - abort(404) + raise NotFound() existing = model.get_prototype_permission(org, prototypeid) if not existing: - abort(404) + raise NotFound() details = request.get_json() role_name = details['role'] prototype = model.update_prototype_permission(org, prototypeid, role_name) if not prototype: - abort(404) + raise NotFound() log_prototype_action('modify_prototype_permission', orgname, prototype, original_role=existing.role.name) org_members = model.get_organization_member_set(orgname) return prototype_view(prototype, org_members) - abort(403) + raise Unauthorized() diff --git a/endpoints/api/repository.py b/endpoints/api/repository.py index 9dba49a47..edca0f3db 100644 --- a/endpoints/api/repository.py +++ b/endpoints/api/repository.py @@ -2,13 +2,12 @@ import logging import json from flask import current_app, request -from flask.ext.restful import reqparse, abort from data import model from endpoints.api import (truthy_bool, format_date, nickname, log_action, validate_json_request, require_repo_read, require_repo_write, require_repo_admin, RepositoryParamResource, resource, query_param, parse_args, ApiResource, - request_error, require_scope) + request_error, require_scope, Unauthorized, NotFound) from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission, AdministerRepositoryPermission, CreateRepositoryPermission) from auth.auth import process_auth @@ -73,7 +72,7 @@ class RepositoryList(ApiResource): existing = model.get_repository(namespace_name, repository_name) if existing: - return request_error(message='Repository already exists') + raise request_error(message='Repository already exists') visibility = req['visibility'] @@ -89,7 +88,7 @@ class RepositoryList(ApiResource): 'name': repository_name }, 201 - abort(403) + raise Unauthorized() @nickname('listRepos') @parse_args @@ -213,7 +212,7 @@ class Repository(RepositoryParamResource): 'status_token': repo.badge_token if not is_public else '' } - abort(404) # Not found + raise NotFound() @require_repo_write @nickname('updateRepo') @@ -232,7 +231,7 @@ class Repository(RepositoryParamResource): return { 'success': True } - abort(404) # Not found + raise NotFound() @require_repo_admin @nickname('deleteRepository') diff --git a/endpoints/api/repotoken.py b/endpoints/api/repotoken.py index 577abf638..e8df11d7e 100644 --- a/endpoints/api/repotoken.py +++ b/endpoints/api/repotoken.py @@ -1,10 +1,9 @@ import logging from flask import request -from flask.ext.restful import abort from endpoints.api import (resource, nickname, require_repo_admin, RepositoryParamResource, - log_action, validate_json_request) + log_action, validate_json_request, NotFound) from data import model @@ -97,7 +96,7 @@ class RepositoryToken(RepositoryParamResource): try: perm = model.get_repo_delegate_token(namespace, repository, code) except model.InvalidTokenException: - abort(404) + raise NotFound() return token_view(perm) diff --git a/endpoints/api/robot.py b/endpoints/api/robot.py index c577a6685..fe546256f 100644 --- a/endpoints/api/robot.py +++ b/endpoints/api/robot.py @@ -1,6 +1,5 @@ -from flask.ext.restful import abort - -from endpoints.api import resource, nickname, ApiResource, log_action, related_user_resource +from endpoints.api import (resource, nickname, ApiResource, log_action, related_user_resource, + Unauthorized) from auth.permissions import AdministerOrganizationPermission, OrganizationMemberPermission from auth.auth_context import get_authenticated_user from data import model @@ -21,6 +20,9 @@ class UserRobotList(ApiResource): def get(self): """ List the available robots for the user. """ user = get_authenticated_user() + if not user: + raise Unauthorized() + robots = model.list_entity_robots(user.username) return { 'robots': [robot_view(name, password) for name, password in robots] @@ -34,6 +36,9 @@ class UserRobot(ApiResource): def put(self, robot_shortname): """ Create a new user robot with the specified name. """ parent = get_authenticated_user() + if not parent: + raise Unauthorized() + robot, password = model.create_robot(robot_shortname, parent) log_action('create_robot', parent.username, {'robot': robot_shortname}) return robot_view(robot.username, password), 201 @@ -42,6 +47,9 @@ class UserRobot(ApiResource): def delete(self, robot_shortname): """ Delete an existing robot. """ parent = get_authenticated_user() + if not parent: + raise Unauthorized() + model.delete_robot(format_robot_username(parent.username, robot_shortname)) log_action('delete_robot', parent.username, {'robot': robot_shortname}) return 'Deleted', 204 @@ -61,7 +69,7 @@ class OrgRobotList(ApiResource): 'robots': [robot_view(name, password) for name, password in robots] } - abort(403) + raise Unauthorized() @resource('/v1/organization//robots/') @@ -78,7 +86,7 @@ class OrgRobot(ApiResource): log_action('create_robot', orgname, {'robot': robot_shortname}) return robot_view(robot.username, password), 201 - abort(403) + raise Unauthorized() @nickname('deleteOrgRobot') def delete(self, orgname, robot_shortname): @@ -89,4 +97,4 @@ class OrgRobot(ApiResource): log_action('delete_robot', orgname, {'robot': robot_shortname}) return 'Deleted', 204 - abort(403) + raise Unauthorized() diff --git a/endpoints/api/subscribe.py b/endpoints/api/subscribe.py index 02c9e2a09..13de3fdec 100644 --- a/endpoints/api/subscribe.py +++ b/endpoints/api/subscribe.py @@ -1,10 +1,9 @@ import logging import stripe -from endpoints.api import request_error, log_action +from endpoints.api import request_error, log_action, NotFound from data import model from data.plans import PLANS -from util.http import abort logger = logging.getLogger(__name__) @@ -31,13 +30,13 @@ def subscribe(user, plan, token, require_business_plan): if not plan_found or plan_found['deprecated']: logger.warning('Plan not found or deprecated: %s', plan) - abort(404) + raise NotFound() if (require_business_plan and not plan_found['bus_features'] and not plan_found['price'] == 0): logger.warning('Business attempting to subscribe to personal plan: %s', user.username) - return request_error(message='No matching plan found') + raise request_error(message='No matching plan found') private_repos = model.get_private_repo_count(user.username) diff --git a/endpoints/api/tag.py b/endpoints/api/tag.py index a175458ff..371fba2dd 100644 --- a/endpoints/api/tag.py +++ b/endpoints/api/tag.py @@ -1,7 +1,5 @@ -from flask.ext.restful import abort - from endpoints.api import (resource, nickname, require_repo_read, require_repo_admin, - RepositoryParamResource, log_action) + RepositoryParamResource, log_action, NotFound) from endpoints.api.image import image_view from data import model from auth.auth_context import get_authenticated_user @@ -36,7 +34,7 @@ class RepositoryTagImages(RepositoryParamResource): try: tag_image = model.get_tag_image(namespace, repository, tag) except model.DataModelException: - abort(404) + raise NotFound() parent_images = model.get_parent_images(tag_image) diff --git a/endpoints/api/team.py b/endpoints/api/team.py index 3049e4254..ccdc3b31b 100644 --- a/endpoints/api/team.py +++ b/endpoints/api/team.py @@ -1,8 +1,7 @@ from flask import request -from flask.ext.restful import abort from endpoints.api import (resource, nickname, ApiResource, validate_json_request, request_error, - log_action) + log_action, Unauthorized, NotFound) from auth.permissions import AdministerOrganizationPermission, ViewTeamPermission from auth.auth_context import get_authenticated_user from data import model @@ -95,7 +94,7 @@ class OrganizationTeam(ApiResource): return team_view(orgname, team), 200 - abort(403) + raise Unauthorized() @nickname('deleteOrganizationTeam') def delete(self, orgname, teamname): @@ -106,7 +105,7 @@ class OrganizationTeam(ApiResource): log_action('org_delete_team', orgname, {'team': teamname}) return 'Deleted', 204 - abort(403) + raise Unauthorized() @resource('/v1/organization//team//members') @@ -123,7 +122,7 @@ class TeamMemberList(ApiResource): try: team = model.get_organization_team(orgname, teamname) except model.InvalidTeamException: - abort(404) + raise NotFound() members = model.get_organization_team_members(team.id) return { @@ -131,7 +130,7 @@ class TeamMemberList(ApiResource): 'can_edit': edit_permission.can() } - abort(403) + raise Unauthorized() @resource('/v1/organization//team//members/') @@ -149,19 +148,19 @@ class TeamMember(ApiResource): try: team = model.get_organization_team(orgname, teamname) except model.InvalidTeamException: - abort(404) + raise NotFound() # Find the user. user = model.get_user(membername) if not user: - return request_error(message='Unknown user') + raise request_error(message='Unknown user') # Add the user to the team. model.add_user_to_team(user, team) log_action('org_add_team_member', orgname, {'member': membername, 'team': teamname}) return member_view(user) - abort(403) + raise Unauthorized() @nickname('deleteOrganizationTeamMember') def delete(self, orgname, teamname, membername): @@ -174,4 +173,4 @@ class TeamMember(ApiResource): log_action('org_remove_team_member', orgname, {'member': membername, 'team': teamname}) return 'Deleted', 204 - abort(403) + raise Unauthorized() diff --git a/endpoints/api/trigger.py b/endpoints/api/trigger.py index f4bce82be..2a56064b8 100644 --- a/endpoints/api/trigger.py +++ b/endpoints/api/trigger.py @@ -2,14 +2,13 @@ import json import logging from flask import request, url_for -from flask.ext.restful import abort from urllib import quote from urlparse import urlunparse from app import app from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin, log_action, request_error, query_param, parse_args, - validate_json_request, api) + validate_json_request, api, Unauthorized, NotFound, InvalidRequest) from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus from endpoints.common import start_build from endpoints.trigger import (BuildTrigger, TriggerDeactivationException, @@ -51,7 +50,7 @@ class BuildTrigger(RepositoryParamResource): try: trigger = model.get_build_trigger(namespace, repository, trigger_uuid) except model.InvalidBuildTriggerException: - abort(404) + raise NotFound() return trigger_view(trigger) @@ -62,8 +61,7 @@ class BuildTrigger(RepositoryParamResource): try: trigger = model.get_build_trigger(namespace, repository, trigger_uuid) except model.InvalidBuildTriggerException: - abort(404) - return + raise NotFound() handler = BuildTrigger.get_trigger_for_service(trigger.service.name) config_dict = json.loads(trigger.config) @@ -102,8 +100,7 @@ class BuildTriggerSubdirs(RepositoryParamResource): try: trigger = model.get_build_trigger(namespace, repository, trigger_uuid) except model.InvalidBuildTriggerException: - abort(404) - return + raise NotFound() handler = BuildTrigger.get_trigger_for_service(trigger.service.name) user_permission = UserPermission(trigger.connected_user.username) @@ -122,7 +119,7 @@ class BuildTriggerSubdirs(RepositoryParamResource): 'message': exc.msg } else: - abort(403) + raise Unauthorized() @resource('/v1/repository//trigger//activate') @@ -145,12 +142,12 @@ class BuildTriggerActivate(RepositoryParamResource): try: trigger = model.get_build_trigger(namespace, repository, trigger_uuid) except model.InvalidBuildTriggerException: - abort(404) + raise NotFound() handler = BuildTrigger.get_trigger_for_service(trigger.service.name) existing_config_dict = json.loads(trigger.config) if handler.is_active(existing_config_dict): - abort(400) + raise InvalidRequest('Trigger config is not sufficient for activation.') user_permission = UserPermission(trigger.connected_user.username) if user_permission.can(): @@ -173,7 +170,7 @@ class BuildTriggerActivate(RepositoryParamResource): trigger.auth_token, new_config_dict) except TriggerActivationException as exc: token.delete_instance() - return request_error(message=exc.message) + raise request_error(message=exc.message) # Save the updated config. trigger.config = json.dumps(final_config) @@ -189,7 +186,7 @@ class BuildTriggerActivate(RepositoryParamResource): return trigger_view(trigger) else: - abort(403) + raise Unauthorized() @resource('/v1/repository//trigger//start') @@ -203,12 +200,12 @@ class ActivateBuildTrigger(RepositoryParamResource): try: trigger = model.get_build_trigger(namespace, repository, trigger_uuid) except model.InvalidBuildTriggerException: - abort(404) + raise NotFound() handler = BuildTrigger.get_trigger_for_service(trigger.service.name) existing_config_dict = json.loads(trigger.config) if not handler.is_active(existing_config_dict): - abort(400) + raise InvalidRequest('Trigger is not active.') specs = handler.manual_start(trigger.auth_token, json.loads(trigger.config)) dockerfile_id, tags, name, subdir = specs @@ -253,7 +250,7 @@ class BuildTriggerSources(RepositoryParamResource): try: trigger = model.get_build_trigger(namespace, repository, trigger_uuid) except model.InvalidBuildTriggerException: - abort(404) + raise NotFound() user_permission = UserPermission(trigger.connected_user.username) if user_permission.can(): @@ -263,4 +260,4 @@ class BuildTriggerSources(RepositoryParamResource): 'sources': trigger_handler.list_build_sources(trigger.auth_token) } else: - abort(403) \ No newline at end of file + raise Unauthorized() \ No newline at end of file diff --git a/endpoints/api/user.py b/endpoints/api/user.py index b1d9a5800..d9c31b570 100644 --- a/endpoints/api/user.py +++ b/endpoints/api/user.py @@ -2,13 +2,12 @@ import logging import stripe from flask import request -from flask.ext.restful import abort from flask.ext.login import logout_user from flask.ext.principal import identity_changed, AnonymousIdentity from app import app from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error, - log_action, internal_only) + log_action, internal_only, NotFound) from endpoints.api.subscribe import subscribe from endpoints.common import common_login from data import model @@ -139,7 +138,7 @@ class User(ApiResource): new_email = user_data['email'] if model.find_user_by_email(new_email): # Email already used. - return request_error(message='E-mail address already used') + raise request_error(message='E-mail address already used') logger.debug('Sending email to change email address for user: %s', user.username) @@ -147,7 +146,7 @@ class User(ApiResource): send_change_email(user.username, user_data['email'], code.code) except model.InvalidPasswordException, ex: - return request_error(exception=ex) + raise request_error(exception=ex) return user_view(user) @@ -160,7 +159,7 @@ class User(ApiResource): existing_user = model.get_user(user_data['username']) if existing_user: - return request_error(message='The username already exists') + raise request_error(message='The username already exists') try: new_user = model.create_user(user_data['username'], user_data['password'], @@ -169,7 +168,7 @@ class User(ApiResource): send_confirmation_email(new_user.username, new_user.email, code.code) return 'Created', 201 except model.DataModelException as ex: - return request_error(exception=ex) + raise request_error(exception=ex) @resource('/v1/user/private') @@ -257,13 +256,13 @@ class ConvertToOrganization(ApiResource): # Ensure that the new admin user is the not user being converted. admin_username = convert_data['adminUser'] if admin_username == user.username: - return request_error(reason='invaliduser', + raise request_error(reason='invaliduser', message='The admin user is not valid') # Ensure that the sign in credentials work. admin_password = convert_data['adminPassword'] if not model.verify_user(admin_username, admin_password): - return request_error(reason='invaliduser', + raise request_error(reason='invaliduser', message='The admin user credentials are not valid') # Subscribe the organization to the new plan. @@ -310,7 +309,7 @@ class Signin(ApiResource): """ Sign in the user with the specified credentials. """ signin_data = request.get_json() if not signin_data: - abort(404) + raise NotFound() username = signin_data['username'] password = signin_data['password'] diff --git a/endpoints/api/webhook.py b/endpoints/api/webhook.py index aabd2e335..52a2dc939 100644 --- a/endpoints/api/webhook.py +++ b/endpoints/api/webhook.py @@ -1,10 +1,9 @@ import json from flask import request -from flask.ext.restful import abort from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin, - log_action, validate_json_request, api) + log_action, validate_json_request, api, NotFound) from data import model @@ -63,7 +62,7 @@ class Webhook(RepositoryParamResource): try: webhook = model.get_webhook(namespace, repository, public_id) except model.InvalidWebhookException: - abort(404) + raise NotFound() return webhook_view(webhook) diff --git a/endpoints/common.py b/endpoints/common.py index 33feea2a2..14103d9c5 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -77,12 +77,6 @@ def handle_dme(ex): return make_response(ex.message, 400) -@app.errorhandler(KeyError) -def handle_dme_key_error(ex): - logger.exception(ex) - return make_response('Invalid key: %s' % ex.message, 400) - - def generate_csrf_token(): if '_csrf_token' not in session: session['_csrf_token'] = base64.b64encode(os.urandom(48))