2014-03-11 16:57:33 +00:00
|
|
|
import logging
|
2014-03-13 00:33:57 +00:00
|
|
|
import json
|
2014-03-11 16:57:33 +00:00
|
|
|
|
2014-03-13 00:33:57 +00:00
|
|
|
from flask import Blueprint, request, make_response
|
2014-03-11 16:57:33 +00:00
|
|
|
from flask.ext.restful import Resource, abort, Api, reqparse
|
|
|
|
from flask.ext.restful.utils.cors import crossdomain
|
2014-03-10 22:30:41 +00:00
|
|
|
from calendar import timegm
|
|
|
|
from email.utils import formatdate
|
2014-03-11 03:54:55 +00:00
|
|
|
from functools import partial, wraps
|
|
|
|
from jsonschema import validate, ValidationError
|
|
|
|
|
|
|
|
from data import model
|
|
|
|
from util.names import parse_namespace_repository
|
|
|
|
from auth.permissions import (ReadRepositoryPermission,
|
|
|
|
ModifyRepositoryPermission,
|
|
|
|
AdministerRepositoryPermission)
|
2014-03-12 16:37:06 +00:00
|
|
|
from auth import scopes
|
2014-03-13 00:33:57 +00:00
|
|
|
from auth.auth_context import get_authenticated_user
|
|
|
|
from auth.auth import process_oauth
|
2014-03-11 03:54:55 +00:00
|
|
|
|
2014-03-10 22:30:41 +00:00
|
|
|
|
2014-03-11 16:57:33 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
api_bp = Blueprint('api', __name__)
|
|
|
|
api = Api(api_bp)
|
2014-03-13 00:33:57 +00:00
|
|
|
api.decorators = [process_oauth,
|
|
|
|
crossdomain(origin='*', headers=['Authorization', 'Content-Type'])]
|
2014-03-11 16:57:33 +00:00
|
|
|
|
2014-03-10 22:30:41 +00:00
|
|
|
|
2014-03-11 16:57:33 +00:00
|
|
|
def resource(*urls, **kwargs):
|
|
|
|
def wrapper(api_resource):
|
|
|
|
api.add_resource(api_resource, *urls, **kwargs)
|
|
|
|
return api_resource
|
|
|
|
return wrapper
|
2014-03-10 22:30:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
def truthy_bool(param):
|
|
|
|
return param not in {False, 'false', 'False', '0', 'FALSE', '', 'null'}
|
|
|
|
|
|
|
|
|
|
|
|
def format_date(date):
|
|
|
|
""" Output an RFC822 date format. """
|
|
|
|
return formatdate(timegm(date.utctimetuple()))
|
|
|
|
|
|
|
|
|
|
|
|
def add_method_metadata(name, value):
|
|
|
|
def modifier(func):
|
|
|
|
if '__api_metadata' not in dir(func):
|
2014-03-11 03:54:55 +00:00
|
|
|
func.__api_metadata = {}
|
|
|
|
func.__api_metadata[name] = value
|
2014-03-10 22:30:41 +00:00
|
|
|
return func
|
|
|
|
return modifier
|
|
|
|
|
|
|
|
|
|
|
|
def method_metadata(func, name):
|
|
|
|
if '__api_metadata' in dir(func):
|
2014-03-11 03:54:55 +00:00
|
|
|
return func.__api_metadata.get(name, None)
|
2014-03-10 22:30:41 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
nickname = partial(add_method_metadata, 'nickname')
|
2014-03-13 19:19:49 +00:00
|
|
|
internal_api = add_method_metadata('internal_api', True)
|
2014-03-10 22:30:41 +00:00
|
|
|
|
|
|
|
|
2014-03-11 19:20:03 +00:00
|
|
|
def query_param(name, help_str, type=reqparse.text_type, default=None,
|
|
|
|
choices=(), required=False):
|
2014-03-11 16:57:33 +00:00
|
|
|
def add_param(func):
|
|
|
|
if '__api_query_params' not in dir(func):
|
|
|
|
func.__api_query_params = []
|
|
|
|
func.__api_query_params.append({
|
|
|
|
'name': name,
|
|
|
|
'type': type,
|
|
|
|
'help': help_str,
|
|
|
|
'default': default,
|
|
|
|
'choices': choices,
|
|
|
|
'required': required,
|
|
|
|
})
|
|
|
|
return func
|
|
|
|
return add_param
|
|
|
|
|
|
|
|
|
|
|
|
def parse_args(func):
|
|
|
|
@wraps(func)
|
|
|
|
def wrapper(self, *args, **kwargs):
|
|
|
|
if '__api_query_params' not in dir(func):
|
|
|
|
abort(400)
|
|
|
|
|
|
|
|
parser = reqparse.RequestParser()
|
|
|
|
for arg_spec in func.__api_query_params:
|
|
|
|
parser.add_argument(**arg_spec)
|
|
|
|
parsed_args = parser.parse_args()
|
|
|
|
|
|
|
|
return func(self, parsed_args, *args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2014-03-11 03:54:55 +00:00
|
|
|
def parse_repository_name(func):
|
|
|
|
@wraps(func)
|
|
|
|
def wrapper(repository, *args, **kwargs):
|
|
|
|
(namespace, repository) = parse_namespace_repository(repository)
|
|
|
|
return func(namespace, repository, *args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2014-03-12 20:31:37 +00:00
|
|
|
class ApiResource(Resource):
|
|
|
|
def options(self):
|
|
|
|
return None, 200
|
|
|
|
|
|
|
|
|
|
|
|
class RepositoryParamResource(ApiResource):
|
2014-03-11 03:54:55 +00:00
|
|
|
method_decorators = [parse_repository_name]
|
|
|
|
|
|
|
|
|
2014-03-12 16:37:06 +00:00
|
|
|
def require_repo_permission(permission_class, scope, allow_public=False):
|
2014-03-11 03:54:55 +00:00
|
|
|
def wrapper(func):
|
2014-03-12 16:37:06 +00:00
|
|
|
@add_method_metadata('oauth2_scope', scope)
|
2014-03-11 03:54:55 +00:00
|
|
|
@wraps(func)
|
|
|
|
def wrapped(self, namespace, repository, *args, **kwargs):
|
2014-03-13 00:33:57 +00:00
|
|
|
logger.debug('Checking permission %s for repo: %s/%s', permission_class, namespace,
|
|
|
|
repository)
|
2014-03-11 03:54:55 +00:00
|
|
|
permission = permission_class(namespace, repository)
|
|
|
|
if (permission.can() or
|
|
|
|
(allow_public and
|
|
|
|
model.repository_is_public(namespace, repository))):
|
|
|
|
return func(self, namespace, repository, *args, **kwargs)
|
|
|
|
abort(403)
|
|
|
|
return wrapped
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2014-03-12 16:37:06 +00:00
|
|
|
require_repo_read = require_repo_permission(ReadRepositoryPermission, scopes.READ_REPO, True)
|
|
|
|
require_repo_write = require_repo_permission(ModifyRepositoryPermission, scopes.WRITE_REPO)
|
|
|
|
require_repo_admin = require_repo_permission(AdministerRepositoryPermission, scopes.ADMIN_REPO)
|
2014-03-11 03:54:55 +00:00
|
|
|
|
|
|
|
|
2014-03-13 00:33:57 +00:00
|
|
|
def require_scope(scope_object):
|
|
|
|
def wrapper(func):
|
|
|
|
@add_method_metadata('oauth2_scope', scope_object['scope'])
|
|
|
|
@wraps(func)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2014-03-11 03:54:55 +00:00
|
|
|
def validate_json_request(schema_name):
|
|
|
|
def wrapper(func):
|
|
|
|
@add_method_metadata('request_schema', schema_name)
|
|
|
|
@wraps(func)
|
2014-03-13 00:33:57 +00:00
|
|
|
def wrapped(self, *args, **kwargs):
|
2014-03-11 03:54:55 +00:00
|
|
|
schema = self.schemas[schema_name]
|
|
|
|
try:
|
|
|
|
validate(request.get_json(), schema)
|
2014-03-13 00:33:57 +00:00
|
|
|
return func(self, *args, **kwargs)
|
2014-03-11 03:54:55 +00:00
|
|
|
except ValidationError as ex:
|
|
|
|
abort(400, message=ex.message)
|
|
|
|
return wrapped
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2014-03-13 00:33:57 +00:00
|
|
|
def request_error(exception=None, **kwargs):
|
|
|
|
data = kwargs.copy()
|
|
|
|
if exception:
|
|
|
|
data['message'] = exception.message
|
|
|
|
|
|
|
|
return json.dumps(data), 400
|
|
|
|
|
|
|
|
|
2014-03-11 03:54:55 +00:00
|
|
|
def log_action(kind, user_or_orgname, metadata={}, repo=None):
|
2014-03-13 00:33:57 +00:00
|
|
|
performer = get_authenticated_user()
|
2014-03-11 19:20:03 +00:00
|
|
|
model.log_action(kind, user_or_orgname, performer=performer, ip=request.remote_addr,
|
|
|
|
metadata=metadata, repository=repo)
|
2014-03-11 03:54:55 +00:00
|
|
|
|
|
|
|
|
2014-03-10 22:30:41 +00:00
|
|
|
import endpoints.api.legacy
|
|
|
|
|
2014-03-14 19:35:20 +00:00
|
|
|
import endpoints.api.billing
|
2014-03-13 20:31:37 +00:00
|
|
|
import endpoints.api.build
|
2014-03-14 17:24:01 +00:00
|
|
|
import endpoints.api.discovery
|
2014-03-14 17:06:58 +00:00
|
|
|
import endpoints.api.image
|
2014-03-14 17:24:01 +00:00
|
|
|
import endpoints.api.permission
|
2014-03-14 18:51:18 +00:00
|
|
|
import endpoints.api.prototype
|
2014-03-14 17:24:01 +00:00
|
|
|
import endpoints.api.repository
|
|
|
|
import endpoints.api.repotoken
|
|
|
|
import endpoints.api.search
|
2014-03-14 17:06:58 +00:00
|
|
|
import endpoints.api.tag
|
2014-03-14 18:20:51 +00:00
|
|
|
import endpoints.api.team
|
2014-03-14 17:24:01 +00:00
|
|
|
import endpoints.api.trigger
|
2014-03-14 18:20:51 +00:00
|
|
|
import endpoints.api.organization
|
2014-03-14 17:24:01 +00:00
|
|
|
import endpoints.api.user
|
|
|
|
import endpoints.api.webhook
|