def register_service(endpoint_type, settings): """Registers a service in cornice, for the given type. """ path_pattern = getattr(viewset, '%s_path' % endpoint_type) path_values = {'resource_name': resource_name} path = path_pattern.format(**path_values) name = viewset.get_service_name(endpoint_type, resource_cls) service = Service(name, path, depth=depth, **viewset.get_service_arguments()) # Attach viewset and resource to the service for later reference. service.viewset = viewset service.resource = resource_cls service.type = endpoint_type # Attach collection and record paths. service.collection_path = viewset.collection_path.format(**path_values) service.record_path = (viewset.record_path.format(**path_values) if viewset.record_path is not None else None) methods = getattr(viewset, '%s_methods' % endpoint_type) for method in methods: if not viewset.is_endpoint_enabled( endpoint_type, resource_name, method.lower(), settings): continue argument_getter = getattr(viewset, '%s_arguments' % endpoint_type) view_args = argument_getter(resource_cls, method) view = viewset.get_view(endpoint_type, method.lower()) service.add_view(method, view, klass=resource_cls, **view_args) return service
def register_service(endpoint_type, settings): """Registers a service in cornice, for the given type. """ path_pattern = getattr(viewset, "{}_path".format(endpoint_type)) path_values = {"resource_name": resource_name} path = path_pattern.format_map(path_values) name = viewset.get_service_name(endpoint_type, resource_cls) service = Service(name, path, depth=depth, **viewset.get_service_arguments()) # Attach viewset and resource to the service for later reference. service.viewset = viewset service.resource = resource_cls service.type = endpoint_type # Attach collection and record paths. service.collection_path = viewset.collection_path.format_map( path_values) service.record_path = (viewset.record_path.format_map(path_values) if viewset.record_path is not None else None) methods = getattr(viewset, "{}_methods".format(endpoint_type)) for method in methods: if not viewset.is_endpoint_enabled(endpoint_type, resource_name, method.lower(), settings): continue argument_getter = getattr(viewset, "{}_arguments".format(endpoint_type)) view_args = argument_getter(resource_cls, method) view = viewset.get_view(endpoint_type, method.lower()) service.add_view(method, view, klass=resource_cls, **view_args) # We support JSON-patch on PATCH views. Since the body payload # of JSON Patch is not a dict (mapping) but an array, we can't # use the same schema as for other PATCH protocols. We add another # dedicated view for PATCH, but targetting a different content_type # predicate. if method.lower() == "patch": view_args["content_type"] = "application/json-patch+json" view_args["schema"] = JsonPatchRequestSchema() service.add_view(method, view, klass=resource_cls, **view_args) return service
from pyramid.security import NO_PERMISSION_REQUIRED, Authenticated from kinto.authorization import PERMISSIONS_INHERITANCE_TREE from kinto.core import Service, utils as core_utils permissions = Service(name='permissions', description='List of user permissions', path='/permissions') @permissions.get(permission=NO_PERMISSION_REQUIRED) def permissions_get(request): # Invert the permissions inheritance tree. perms_descending_tree = {} for obtained, obtained_from in PERMISSIONS_INHERITANCE_TREE.items(): on_resource, obtained_perm = obtained.split(':', 1) for from_resource, perms in obtained_from.items(): for perm in perms: perms_descending_tree.setdefault(from_resource, {})\ .setdefault(perm, {})\ .setdefault(on_resource, set())\ .add(obtained_perm) # Obtain current principals. principals = request.effective_principals if Authenticated in principals: # Since this view does not require any permission (can be used to # obtain public users permissions), we have to add the prefixed userid # among the principals (see :mode:`kinto.core.authentication`) userid = request.prefixed_userid principals.append(userid)
class BatchResponse(colander.MappingSchema): body = BatchResponseBodySchema() class ErrorResponseSchema(colander.MappingSchema): body = ErrorSchema() batch_responses = { '200': BatchResponse(description='Return a list of operation responses.'), '400': ErrorResponseSchema(description='The request was badly formatted.'), 'default': ErrorResponseSchema(description='an unknown error occurred.') } batch = Service(name='batch', path='/batch', description='Batch operations') @batch.post(schema=BatchRequest, validators=(colander_validator, ), permission=NO_PERMISSION_REQUIRED, tags=['Batch'], operation_id='batch', response_schemas=batch_responses) def post_batch(request): requests = request.validated['body']['requests'] request.log_context(batch_size=len(requests)) limit = request.registry.settings['batch_max_requests'] if limit and len(requests) > int(limit):
Querystring schema for the login endpoint. """ callback = URL() scope = colander.SchemaNode(colander.String()) prompt = colander.SchemaNode(colander.String(), validator=colander.Regex("none"), missing=colander.drop) class LoginSchema(colander.MappingSchema): querystring = LoginQuerystringSchema() login = Service(name="openid_login", path="/openid/{provider}/login", description="Initiate the OAuth2 login") @login.get( schema=LoginSchema(), validators=(colander_validator, provider_validator), response_schemas=response_schemas, ) def get_login(request): """Initiates to login dance for the specified scopes and callback URI using appropriate redirections.""" # Settings. provider = request.matchdict["provider"] settings_prefix = "multiauth.policy.%s." % provider
import colander from cornice.validators import (colander_validator, colander_body_validator) from pyramid import httpexceptions from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.settings import aslist from urllib.parse import urlencode, urlparse from kinto.core import Service, utils from kinto.core.errors import ERRORS, http_error from kinto_portier.crypto import encrypt from kinto_portier.utils import portier_conf from portier import get_verified_email login = Service(name='portier-login', path='/portier/login') verify = Service(name='portier-verify', path='/portier/verify') def persist_nonce(request): """Persist arbitrary string in cache. It will be matched when the user returns from the OAuth server login page. """ nonce = uuid.uuid4().hex redirect_url = request.validated['redirect'] expiration = float(portier_conf(request, 'cache_ttl_seconds')) cache = request.registry.cache cache.set("portier:nonce:%s" % nonce, redirect_url, expiration)
import colander from pyramid.security import NO_PERMISSION_REQUIRED, Authenticated from kinto.core import Service hello = Service(name="hello", path='/', description="Welcome") class HelloResponseSchema(colander.MappingSchema): body = colander.SchemaNode(colander.Mapping(unknown='preserve')) hello_response_schemas = { '200': HelloResponseSchema( description='Return information about the running Instance.') } @hello.get(permission=NO_PERMISSION_REQUIRED, tags=['Utilities'], operation_id='server_info', response_schemas=hello_response_schemas) def get_hello(request): """Return information regarding the current instance.""" settings = request.registry.settings project_name = settings['project_name'] project_version = settings['project_version'] data = dict(project_name=project_name, project_version=project_version, http_api_version=settings['http_api_version'],
import requests from cornice.validators import colander_validator from pyramid import httpexceptions from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.settings import aslist from kinto.core import Service from kinto.core.errors import http_error, ERRORS, json_error_handler, raise_invalid from kinto.core.resource.schema import URL from kinto_facebook.utils import facebook_conf logger = logging.getLogger(__name__) login = Service(name='facebook-login', path='/facebook/login', error_handler=json_error_handler) token = Service(name='facebook-token', path='/facebook/token', error_handler=json_error_handler) def persist_state(request): """Persist arbitrary string in cache. It will be matched when the user returns from the OAuth server login page. """ state = uuid.uuid4().hex redirect_url = request.validated['querystring']['redirect'] expiration = float(facebook_conf(request, 'cache_ttl_seconds'))
import colander from pyramid.security import NO_PERMISSION_REQUIRED, Authenticated from kinto.core import Service hello = Service(name='hello', path='/', description='Welcome') class HelloResponseSchema(colander.MappingSchema): body = colander.SchemaNode(colander.Mapping(unknown='preserve')) hello_response_schemas = { '200': HelloResponseSchema( description='Return information about the running Instance.') } @hello.get(permission=NO_PERMISSION_REQUIRED, tags=['Utilities'], operation_id='server_info', response_schemas=hello_response_schemas) def get_hello(request): """Return information regarding the current instance.""" settings = request.registry.settings project_name = settings['project_name'] project_version = settings['project_version'] data = dict(project_name=project_name, project_version=project_version, http_api_version=settings['http_api_version'],
from kinto.core.storage import exceptions as storage_exceptions from ..mails import Emailer from ..utils import ( cache_reset_password, delete_cached_validation_key, get_cached_validation_key, hash_password, ) from . import DEFAULT_EMAIL_REGEXP # Account validation (enable in the settings). validation = Service( name="account-validation", path="/accounts/{user_id}/validate/{activation_key}", description="Validate an account", ) def check_validation_key(activation_key, username, registry): """Given a username, compare the activation-key provided with the one from the cache.""" cache_result = get_cached_validation_key(username, registry) if cache_result == activation_key: delete_cached_validation_key( username, registry) # We're done with the activation key. return True return False
import requests from kinto.core import Service, logger search = Service(name="search", path='/github/token', description="Github Access Token") @search.post() def get_search(request): authorization_code = request.POST['authorization_code'] try: payload = { 'client_id': 'bc6a0e70dc379a6313ae', 'client_secret': '99ca1391ae9b8bc0c0a71628629e43ab32c17def', 'code': authorization_code } headers = {"Accept": "application/json"} resp = requests.post('https://github.com/login/oauth/access_token', data=payload, headers=headers) resp.raise_for_status() respjson = resp.json() return respjson except Exception as e: logger.exception(e) return None
import requests from kinto.core import Service, logger from pyramid.httpexceptions import HTTPFound from urllib import parse def get_config_value(request, key): return request.registry.settings["auth0." + key] auth0_authorization = Service(name="auth0_authorization", path='/auth/auth0', description="Auth0 Authorization") @auth0_authorization.get() def get_auth0_authorization(request): clientid = get_config_value(request, "client_id") scope = get_config_value(request, "scope") redirecturi = get_config_value(request, "redirect_uri") authuri = get_config_value(request, "auth_uri") audience = get_config_value(request, "audience") query_string = ("response_type=code" "&client_id={client_id}" "&scope={scope}" "&redirect_uri={redirect_uri}" "&audience={audience}").format( client_id=clientid, scope=parse.quote_plus(scope), redirect_uri=parse.quote_plus(redirecturi),
from kinto.core import Service from kinto.core.authorization import DYNAMIC as DYNAMIC_PERMISSION from kinto_attachment import utils from . import post_attachment_view, delete_attachment_view SINGLE_FILE_FIELD = 'attachment' attachment = Service(name='attachment', description='Attach a file to a record', path=utils.RECORD_PATH + '/attachment', cors_enabled=True, cors_origins='*', factory=utils.AttachmentRouteFactory) @attachment.post(permission=DYNAMIC_PERMISSION) def attachment_post(request): return post_attachment_view(request, SINGLE_FILE_FIELD) @attachment.delete(permission=DYNAMIC_PERMISSION) def attachment_delete(request): return delete_attachment_view(request, SINGLE_FILE_FIELD)
HERE = os.path.dirname(__file__) ORIGIN = os.path.dirname(HERE) class VersionResponseSchema(colander.MappingSchema): body = colander.SchemaNode(colander.Mapping(unknown='preserve')) version_response_schemas = { '200': VersionResponseSchema( description='Return the running Instance version information.') } version = Service(name='version', path='/__version__', description='Version') @version.get(permission=NO_PERMISSION_REQUIRED, tags=['Utilities'], operation_id='__version__', response_schemas=version_response_schemas) def version_view(request): try: return version_view.__json__ except AttributeError: pass location = request.registry.settings['version_json_path'] files = [ location, # Default is current working dir.
from kinto.core import Service from kinto.core.authorization import DYNAMIC as DYNAMIC_PERMISSION from kinto_attachment import utils from . import post_attachment_view, delete_attachment_view SINGLE_FILE_FIELD = 'attachment' attachment = Service(name='attachment', description='Attach a file to a record', path=utils.RECORD_PATH + '/attachment', factory=utils.AttachmentRouteFactory) @attachment.post(permission=DYNAMIC_PERMISSION) def attachment_post(request): return post_attachment_view(request, SINGLE_FILE_FIELD) @attachment.delete(permission=DYNAMIC_PERMISSION) def attachment_delete(request): return delete_attachment_view(request, SINGLE_FILE_FIELD)
from pyramid.httpexceptions import HTTPNotFound, HTTPNotModified from kinto.core import Service, utils from kinto.core.storage import Filter, Sort from amo2kinto.exporter import (write_addons_items, write_plugin_items, write_gfx_items, write_cert_items) from lxml import etree path = ('/{prefix}/{api_ver:\d+}/{application_guid}/{application_ver}/' '{metrics:.*}') PARENT_PATTERN = "/buckets/{bucket}/collections/{collection}" blocklist = Service(name="blocklist", path=path, description="Blocklist data") @blocklist.get() def get_blocklist(request): prefix = request.matchdict['prefix'] api_ver = int(request.matchdict['api_ver']) app = request.matchdict['application_guid'] app_ver = request.matchdict['application_ver'] # 1. Verify that we have a config for that prefix if prefix not in request.registry.amo_resources: raise HTTPNotFound() # Addons blocklist addons_records, addons_last_modified = get_records(request, prefix, 'addons') # Plugins blocklist
class LoginQuerystringSchema(colander.MappingSchema): """ Querystring schema for the login endpoint. """ callback = URL() scope = colander.SchemaNode(colander.String()) class LoginSchema(colander.MappingSchema): querystring = LoginQuerystringSchema() login = Service(name='openid_login', path='/openid/{provider}/login', description='Initiate the OAuth2 login') @login.get(schema=LoginSchema(), validators=(colander_validator, provider_validator), response_schemas=response_schemas) def get_login(request): """Initiates to login dance for the specified scopes and callback URI using appropriate redirections.""" # Settings. provider = request.matchdict['provider'] settings_prefix = 'multiauth.policy.%s.' % provider issuer = request.registry.settings[settings_prefix + 'issuer'] client_id = request.registry.settings[settings_prefix + 'client_id']
logger = logging.getLogger(__name__) class RouteFactory(authorization.RouteFactory): def __init__(self, request): super().__init__(request) records_plural = utils.strip_uri_prefix( request.path.replace("/search", "/records") ) self.permission_object_id = records_plural self.required_permission = "read" search = Service( name="search", path="/buckets/{bucket_id}/collections/{collection_id}/search", description="Search", factory=RouteFactory, ) def search_view(request, **kwargs): bucket_id = request.matchdict["bucket_id"] collection_id = request.matchdict["collection_id"] # algoliasearch doesn't support pagination # https://github.com/algolia/algoliasearch-client-python/issues/365 # # Limit the number of results to return, based on existing Kinto settings. # paginate_by = request.registry.settings.get("paginate_by") # max_fetch_size = request.registry.settings["storage_max_fetch_size"] # if paginate_by is None or paginate_by <= 0:
import logging from concurrent.futures import ThreadPoolExecutor, wait import colander import transaction from pyramid.security import NO_PERMISSION_REQUIRED from kinto.core import Service logger = logging.getLogger(__name__) heartbeat = Service(name='heartbeat', path='/__heartbeat__', description='Server health') class HeartbeatResponseSchema(colander.MappingSchema): body = colander.SchemaNode(colander.Mapping(unknown='preserve')) heartbeat_responses = { '200': HeartbeatResponseSchema(description='Server is working properly.'), '503': HeartbeatResponseSchema(description='One or more subsystems failing.') } @heartbeat.get(permission=NO_PERMISSION_REQUIRED, tags=['Utilities'], operation_id='__heartbeat__', response_schemas=heartbeat_responses)
import colander from pyramid.security import NO_PERMISSION_REQUIRED from cornice.service import get_services from kinto.core import Service from kinto.core.openapi import OpenAPI openapi = Service(name='openapi', path='/__api__', description='OpenAPI description') class OpenAPIResponseSchema(colander.MappingSchema): body = colander.SchemaNode(colander.Mapping(unknown='preserve')) openapi_response_schemas = { '200': OpenAPIResponseSchema( description='Return an OpenAPI description of the running instance.') } @openapi.get(permission=NO_PERMISSION_REQUIRED, response_schemas=openapi_response_schemas, tags=['Utilities'], operation_id='get_openapi_spec') def openapi_view(request): # Only build json once try: return openapi_view.__json__ except AttributeError: openapi_view.__json__ = OpenAPI(get_services(), request).generate()
import os import pkg_resources from ruamel import yaml from pyramid import httpexceptions from pyramid.settings import aslist from pyramid.security import NO_PERMISSION_REQUIRED from kinto.core import Service from kinto.core.utils import recursive_update_dict HERE = os.path.dirname(os.path.abspath(__file__)) ORIGIN = os.path.dirname(os.path.dirname(HERE)) swagger = Service(name="swagger", path='/__api__', description="OpenAPI description") @swagger.get(permission=NO_PERMISSION_REQUIRED) def swagger_view(request): # Only build json once try: return swagger_view.__json__ except AttributeError: pass settings = request.registry.settings # Base swagger spec files = [
from kinto.core import Service from kinto.core.authorization import RouteFactory attachment = Service( name="attachment", description="Attach file to record", path="/attachment", factory=RouteFactory, ) @attachment.post(permission="attach") def attachment_post(request): return {"ok": True} def includeme(config): config.add_cornice_service(attachment)
import colander from pyramid.security import NO_PERMISSION_REQUIRED from cornice.service import get_services from kinto.core import Service from kinto.core.openapi import OpenAPI openapi = Service(name="openapi", path='/__api__', description="OpenAPI description") class OpenAPIResponseSchema(colander.MappingSchema): body = colander.SchemaNode(colander.Mapping(unknown='preserve')) openapi_response_schemas = { '200': OpenAPIResponseSchema( description='Return an OpenAPI description of the running instance.') } @openapi.get(permission=NO_PERMISSION_REQUIRED, response_schemas=openapi_response_schemas, tags=['Utilities'], operation_id='get_openapi_spec') def openapi_view(request): # Only build json once try:
import boto3 import colander from cornice.validators import colander_body_validator from kinto.core import Service from kinto.core.errors import http_error, ERRORS from pyramid import httpexceptions from .aws import get_activity_arn, get_task_token from .storage import update_record from .validators import record_validator stepfunction = Service( name="stepfunction", path='/buckets/{bucket_id}/collections/{collection_id}/records/{record_id}/stepfunction', description="Stepfunction manual step") class RecordSchema(colander.MappingSchema): id = colander.SchemaNode(colander.String()) subject = colander.SchemaNode(colander.String(), missing=colander.drop) stateMachineArn = colander.SchemaNode(colander.String(), missing=colander.drop) activityArn = colander.SchemaNode(colander.String()) taskToken = colander.SchemaNode(colander.String(), missing=colander.drop) reviewer = colander.SchemaNode(colander.String(), missing=colander.drop) class AnswerRequestSchema(colander.MappingSchema): answer = colander.SchemaNode(colander.String(), validator=colander.OneOf(["FAIL", "SUCCEED"]),
class BatchResponse(colander.MappingSchema): body = BatchResponseBodySchema() class ErrorResponseSchema(colander.MappingSchema): body = ErrorSchema() batch_responses = { "200": BatchResponse(description="Return a list of operation responses."), "400": ErrorResponseSchema(description="The request was badly formatted."), "default": ErrorResponseSchema(description="an unknown error occurred."), } batch = Service(name="batch", path="/batch", description="Batch operations") @batch.post( schema=BatchRequest(), validators=(colander_validator, ), content_type=CONTENT_TYPES, permission=NO_PERMISSION_REQUIRED, tags=["Batch"], operation_id="batch", response_schemas=batch_responses, ) def post_batch(request): requests = request.validated["body"]["requests"] request.log_context(batch_size=len(requests))
from kinto.core import Service from .calendar import CALENDARS holidays = Service(name='holidays', path='/holidays{year:(/[0-9]{4})?}', description="Holidays") @holidays.get() def get_holidays(request): year = request.matchdict['year'].strip('/') if year == '': year = None else: year = int(year) holidays = {} for name in sorted(CALENDARS.keys(), reversed=True): try: cal = CALENDARS[name]() country_holidays = cal.holidays(year) except Exception as e: print("{}".format(e)) continue else: for date, title in country_holidays: date_id = date.strftime('%Y-%m-%d') holidays.setdefault(date_id, { "date": date_id, "title": title, "countries": [] })
import logging from concurrent.futures import ThreadPoolExecutor, wait import colander import transaction from pyramid.security import NO_PERMISSION_REQUIRED from kinto.core import Service logger = logging.getLogger(__name__) heartbeat = Service(name="heartbeat", path='/__heartbeat__', description="Server health") class HeartbeatResponseSchema(colander.MappingSchema): body = colander.SchemaNode(colander.Mapping(unknown='preserve')) heartbeat_responses = { '200': HeartbeatResponseSchema(description="Server is working properly."), '503': HeartbeatResponseSchema(description="One or more subsystems failing.") } @heartbeat.get(permission=NO_PERMISSION_REQUIRED, tags=['Utilities'], operation_id='__heartbeat__', response_schemas=heartbeat_responses)
from kinto.core import errors, Service from pyramid.security import NO_PERMISSION_REQUIRED from kinto_fxa.utils import fxa_conf params = Service(name='fxa-oauth-params', path='/fxa-oauth/params', error_handler=errors.json_error_handler) @params.get(permission=NO_PERMISSION_REQUIRED) def fxa_oauth_params(request): """Helper to give Firefox Account configuration information.""" return { 'client_id': fxa_conf(request, 'client_id'), 'oauth_uri': fxa_conf(request, 'oauth_uri'), 'scope': fxa_conf(request, 'required_scope'), }
HERE = os.path.dirname(os.path.abspath(__file__)) ORIGIN = os.path.dirname(os.path.dirname(HERE)) class VersionResponseSchema(colander.MappingSchema): body = colander.SchemaNode(colander.Mapping(unknown='preserve')) version_response_schemas = { '200': VersionResponseSchema( description='Return the running Instance version information.') } version = Service(name="version", path='/__version__', description="Version") @version.get(permission=NO_PERMISSION_REQUIRED, tags=['Utilities'], operation_id='__version__', response_schemas=version_response_schemas) def version_view(request): try: return version_view.__json__ except AttributeError: pass location = request.registry.settings['version_json_path'] files = [ location, # Default is current working dir. os.path.join(ORIGIN, 'version.json'), # Relative to the package root. os.path.join(HERE, 'version.json') # Relative to this file.
import os from kinto.core import Service, utils from pyramid import httpexceptions from pyramid.response import Response from pyramid.security import NO_PERMISSION_REQUIRED from requests_hawk import HawkAuth from kinto.plugins.accounts.authentication import AccountsAuthenticationPolicy from . import HAWK_SESSION_KEY sessions = Service(name='hawk-sessions', path='/hawk-sessions', cors_headers=('Hawk-Session-Token', )) @sessions.post(permission=NO_PERMISSION_REQUIRED) def hawk_sessions(request): """Grab the Hawk Session from another Authentication backend.""" authn = AccountsAuthenticationPolicy() user = authn.authenticated_userid(request) if user is None: response = httpexceptions.HTTPUnauthorized() response.headers.update(authn.forget(request)) return response settings = request.registry.settings hmac_secret = settings['userid_hmac_secret']