diff --git a/README.rst b/README.rst index 3c03cbf9f..bda40da03 100644 --- a/README.rst +++ b/README.rst @@ -659,6 +659,36 @@ auto policy generator detects actions that it would like to add or remove:: .. quick-start-end +Tutorial: Using Custom Authentication +=========================== + +AWS API Gateway routes can be authenticated in multiple ways: +- API Key +- Custom Auth Handler + +# API Key + +.. code-block:: python + + @app.route('/authenticated', methods=['GET'], api_key_required=True) + def authenticated(key): + return {"secure": True} + +Only requests sent with a valid `X-Api-Key` header will be accepted. + +# Custom Auth Handler + +A custom Authorizer is required for this to work, details can be found here; +http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html + +.. code-block:: python + + @app.route('/authenticated', methods=['GET'], authorization_type='CUSTOM', authorizer_id='ab12cd') + def authenticated(key): + return {"secure": True} + +Only requests sent with a valid `X-Api-Key` header will be accepted. + Backlog ======= diff --git a/chalice/app.py b/chalice/app.py index df7d42ec7..adb318e7f 100644 --- a/chalice/app.py +++ b/chalice/app.py @@ -89,11 +89,22 @@ def to_dict(self): class RouteEntry(object): - def __init__(self, view_function, view_name, path, methods): + def __init__( + self, + view_function, + view_name, + path, + methods, + authorization_type=None, + authorizer_id=None, + api_key_required=False): self.view_function = view_function self.view_name = view_name self.uri_pattern = path self.methods = methods + self.authorization_type = authorization_type + self.authorizer_id = authorizer_id + self.api_key_required = api_key_required #: A list of names to extract from path: #: e.g, '/foo/{bar}/{baz}/qux -> ['bar', 'baz'] self.view_args = self._parse_view_args() @@ -132,7 +143,18 @@ def _register_view(view_func): def _add_route(self, path, view_func, **kwargs): name = kwargs.get('name', view_func.__name__) methods = kwargs.get('methods', ['GET']) - self.routes[path] = RouteEntry(view_func, name, path, methods) + authorization_type = kwargs.get('authorization_type', None) + authorizer_id = kwargs.get('authorizer_id', None) + api_key_required = kwargs.get('api_key_required', None) + + self.routes[path] = RouteEntry( + view_func, + name, + path, + methods, + authorization_type, + authorizer_id, + api_key_required) def __call__(self, event, context): # This is what's invoked via lambda. diff --git a/chalice/deployer.py b/chalice/deployer.py index c8a780c61..a90ea31af 100644 --- a/chalice/deployer.py +++ b/chalice/deployer.py @@ -12,6 +12,7 @@ import hashlib import inspect import time +import re from typing import Any, Tuple, Callable, Optional # noqa import botocore.session @@ -572,15 +573,29 @@ def build_resources(self, chalice_trie): rest_api_id=self.rest_api_id), ) + def _camel_convert(self, name): + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + def _configure_resource_route(self, node, http_method): # type: (Dict[str, Any], str) -> None c = self.client - c.put_method( - restApiId=self.rest_api_id, - resourceId=node['resource_id'], - httpMethod=http_method, - authorizationType='NONE' - ) + put_method_cfg = { + 'restApiId': self.rest_api_id, + 'resourceId': node['resource_id'], + 'httpMethod': http_method, + 'authorizationType': 'NONE' + } + + for attr in ['authorizationType', 'authorizerId', 'apiKeyRequired']: + try: + _attr = getattr(node['route_entry'], self._camel_convert(attr)) + if _attr: + put_method_cfg[attr] = _attr + except AttributeError: + pass + + c.put_method(**put_method_cfg) c.put_integration( restApiId=self.rest_api_id, resourceId=node['resource_id'], diff --git a/docs/source/api.rst b/docs/source/api.rst index 0bef92457..b2b5f04d3 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -30,7 +30,7 @@ Chalice app = Chalice(app_name="appname") app.debug = True - .. method:: route(path, \* , [methods [, name]]) + .. method:: route(path, \* , [methods [, name], authorization_type, authorizer_id, api_key_required]) Register a view function for a particular URI path. This method is intended to be used as a decorator for a view function. For example: @@ -64,6 +64,16 @@ Chalice function. You generally do not need to set this value. The name of the view function is used as the default value for the view name. + :param str authorization_type: Optional parameter to specify the type + of authorization used for the view. + + :param str authorizer_id: Optional parameter to specify the identifier + of an Authorizer to use on this view, if the authorization_type is + CUSTOM. + + :param boolean api_key_required: Optional parameter to specify whether + the method required a valid ApiKey + Request ======= diff --git a/docs/source/topics/routing.rst b/docs/source/topics/routing.rst index 3fabd1470..6ceb8bfb2 100644 --- a/docs/source/topics/routing.rst +++ b/docs/source/topics/routing.rst @@ -103,13 +103,13 @@ In the function above, if the user provides a ``?include-greeting=true`` in the HTTP request, then an additional ``greeting`` key will be returned:: $ http https://endpoint/dev/users/bob - + { "name": "bob" } - + $ http https://endpoint/dev/users/bob?include-greeting=true - + { "greeting": "Hello, bob", "name": "bob" diff --git a/requirements-test.txt b/requirements-test.txt index 025438dfd..c6e0d83e6 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,2 +1,3 @@ pytest==2.9.2 py==1.4.31 +pygments==2.1.3