Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add back custom auth #322

Merged
merged 9 commits into from
May 2, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Next Release (TBD)
(`#311 <https://github.com/awslabs/chalice/issues/310>`__)
* Fix content type validation when charset is provided
(`#306 <https://github.com/awslabs/chalice/issues/306>`__)
* Add back custom authorizer support
(`#322 <https://github.com/awslabs/chalice/pull/322>`__)


0.8.0
Expand Down
43 changes: 34 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ Tutorial: Using Custom Authentication
AWS API Gateway routes can be authenticated in multiple ways:

- API Key
- Cognito User Pools
- Custom Auth Handler

API Key
Expand All @@ -960,21 +961,45 @@ Only requests sent with a valid `X-Api-Key` header will be accepted.
Using Amazon Cognito User Pools
-------------------------------

To integrate with cognito user pools, you can use the ``define_authorizer`` method
on the ``app`` object.
To integrate with cognito user pools, you can use the
``CognitoUserPoolAuthorizer`` object:

.. code-block:: python

@app.route('/user-pools', methods=['GET'], authorizer_name='MyPool')
authorizer = CognitoUserPoolAuthorizer(
'MyPool', header='Authorization',
provider_arns=['arn:aws:cognito:...:userpool/name'])

@app.route('/user-pools', methods=['GET'], authorizer=authorizer)
def authenticated():
return {"secure": True}

app.define_authorizer(
name='MyPool',
header='Authorization',
auth_type='cognito_user_pools',
provider_arns=['arn:aws:cognito:...:userpool/name']
)

Note, earlier versions of chalice also have an ``app.define_authorizer``
method as well as an ``authorizer_name`` argument on the ``@app.route(...)``
method. This approach is deprecated in favor of ``CognitoUserPoolAuthorizer``
and the ``authorizer`` argument in the ``@app.route(...)`` method.
``app.define_authorizer`` will be removed in future versions of chalice.


Using Custom Authorizers
------------------------

To integrate with custom authorizers, you can use the ``CustomAuthorizer`` method
on the ``app`` object. You'll need to set the ``authorizer_uri``
to the URI of your lambda function.

.. code-block:: python

authorizer = CustomAuthorizer(
'MyCustomAuth', header='Authorization',
authorizer_uri=('arn:aws:apigateway:region:lambda:path/2015-03-01'
'/functions/arn:aws:lambda:region:account-id:'
'function:FunctionName/invocations'))

@app.route('/user-pools', methods=['GET'], authorizer=authorizer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a different route name here since the other example was for user pools

def authenticated():
return {"secure": True}


Tutorial: Local Mode
Expand Down
68 changes: 63 additions & 5 deletions chalice/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import traceback
import decimal
import warnings
from collections import Mapping

# Implementation note: This file is intended to be a standalone file
Expand Down Expand Up @@ -100,6 +101,59 @@ def __repr__(self):
return 'CaseInsensitiveMapping(%s)' % repr(self._dict)


class Authorizer(object):
name = ''

def to_swagger(self):
raise NotImplementedError("to_swagger")


class CognitoUserPoolAuthorizer(object):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing you meant to inherit from Authorizer here.


_AUTH_TYPE = 'cognito_user_pools'

def __init__(self, name, header, provider_arns):
self.name = name
self._header = header
self._provider_arns = provider_arns

def to_swagger(self):
return {
'in': 'header',
'type': 'apiKey',
'name': self._header,
'x-amazon-apigateway-authtype': self._AUTH_TYPE,
'x-amazon-apigateway-authorizer': {
'type': self._AUTH_TYPE,
'providerARNs': self._provider_arns,
}
}


class CustomAuthorizer(object):

_AUTH_TYPE = 'custom'

def __init__(self, name, header, authorizer_uri, ttl_seconds=300):
self.name = name
self._header = header
self._authorizer_uri = authorizer_uri
self._ttl_seconds = ttl_seconds

def to_swagger(self):
return {
'in': 'header',
'type': 'apiKey',
'name': self._header,
'x-amazon-apigateway-authtype': self._AUTH_TYPE,
'x-amazon-apigateway-authorizer': {
'type': 'token',
'authorizerUri': self._authorizer_uri,
'authorizerResultTtlInSeconds': self._ttl_seconds,
}
}


class CORSConfig(object):
"""A cors configuration to attach to a route."""

Expand Down Expand Up @@ -204,7 +258,7 @@ class RouteEntry(object):
def __init__(self, view_function, view_name, path, methods,
authorizer_name=None,
api_key_required=None, content_types=None,
cors=False):
cors=False, authorizer=None):
self.view_function = view_function
self.view_name = view_name
self.uri_pattern = path
Expand All @@ -225,6 +279,7 @@ def __init__(self, view_function, view_name, path, methods,
elif cors is False:
cors = None
self.cors = cors
self.authorizer = authorizer

def _parse_view_args(self):
if '{' not in self.uri_pattern:
Expand Down Expand Up @@ -284,12 +339,14 @@ def authorizers(self):
return self._authorizers.copy()

def define_authorizer(self, name, header, auth_type, provider_arns=None):
# TODO: double check remaining authorizers. This only handles
# cognito_user_pools.
warnings.warn(
"define_authorizer() is deprecated and will be removed in future "
"versions of chalice. Please use CognitoUserPoolAuthorizer(...) "
"instead", PendingDeprecationWarning)
self._authorizers[name] = {
'header': header,
'auth_type': auth_type,
'provider_arns': provider_arns
'provider_arns': provider_arns,
}

def route(self, path, **kwargs):
Expand All @@ -302,6 +359,7 @@ def _add_route(self, path, view_func, **kwargs):
name = kwargs.pop('name', view_func.__name__)
methods = kwargs.pop('methods', ['GET'])
authorizer_name = kwargs.pop('authorizer_name', None)
authorizer = kwargs.pop('authorizer', None)
api_key_required = kwargs.pop('api_key_required', None)
content_types = kwargs.pop('content_types', ['application/json'])
cors = kwargs.pop('cors', False)
Expand All @@ -319,7 +377,7 @@ def _add_route(self, path, view_func, **kwargs):
"URL paths must be unique." % path)
entry = RouteEntry(view_func, name, path, methods,
authorizer_name, api_key_required,
content_types, cors)
content_types, cors, authorizer)
self.routes[path] = entry

def __call__(self, event, context):
Expand Down
9 changes: 8 additions & 1 deletion chalice/app.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, List, Any, Callable, Union
from typing import Dict, List, Any, Callable, Union, Optional

class ChaliceError(Exception): ...
class ChaliceViewError(ChaliceError):
Expand All @@ -14,6 +14,12 @@ class TooManyRequestsError(ChaliceViewError): ...

ALL_ERRORS = ... # type: List[ChaliceViewError]


class Authorizer:
name = ... # type: str
def to_swagger(self) -> Dict[str, Any]: ...


class CORSConfig:
allow_origin = ... # type: str
allow_headers = ... # type: str
Expand Down Expand Up @@ -63,6 +69,7 @@ class RouteEntry(object):
methods = ... # type: List[str]
uri_pattern = ... # type: str
authorizer_name = ... # type: str
authorizer = ... # type: Optional[Authorizer]
api_key_required = ... # type: bool
content_types = ... # type: List[str]
view_args = ... # type: List[str]
Expand Down
42 changes: 37 additions & 5 deletions chalice/deploy/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Any, List, Dict # noqa

from chalice.app import Chalice, RouteEntry # noqa
from chalice.app import Chalice, RouteEntry, Authorizer # noqa


class SwaggerGenerator(object):
Expand Down Expand Up @@ -44,15 +44,24 @@ def _add_route_paths(self, api, app):
current = self._generate_route_method(view)
if 'security' in current:
self._add_to_security_definition(
current['security'], api, app.authorizers)
current['security'], api, app.authorizers, view)
swagger_for_path[http_method.lower()] = current
if view.cors is not None:
self._add_preflight_request(view, swagger_for_path)

def _add_to_security_definition(self, security, api_config, authorizers):
# type: (Any, Dict[str, Any], Dict[str, Any]) -> None
def _generate_security_from_auth_obj(self, api_config, authorizer):
# type: (Dict[str, Any], Authorizer) -> None
config = authorizer.to_swagger()
api_config.setdefault(
'securityDefinitions', {})[authorizer.name] = config

def _add_to_security_definition(self, security,
api_config, authorizers, view):
# type: (Any, Dict[str, Any], Dict[str, Any], RouteEntry) -> None
if view.authorizer is not None:
self._generate_security_from_auth_obj(api_config, view.authorizer)
return
for auth in security:
# TODO: Add validation checks for unknown auth references.
name = list(auth.keys())[0]
if name == 'api_key':
# This is just the api_key_required=True config
Expand All @@ -62,8 +71,29 @@ def _add_to_security_definition(self, security, api_config, authorizers):
'in': 'header',
} # type: Dict[str, Any]
else:
# This whole section is deprecated and will
# eventually be removed. This handles the
# authorizers that come in via app.define_authorizer(...)
# The only supported type in this method is
# 'cognito_user_pools'. Everything else goes through the
# preferred ``view.authorizer``.
if name not in authorizers:
error_msg = (
"The authorizer '%s' is not defined. "
"Use app.define_authorizer(...) to define an "
"authorizer." % (name)
)
if authorizers:
error_msg += (
' Defined authorizers in this app: %s' %
', '.join(authorizers))
raise ValueError(error_msg)
authorizer_config = authorizers[name]
auth_type = authorizer_config['auth_type']
if auth_type != 'cognito_user_pools':
raise ValueError(
"Unknown auth type: '%s', must be "
"'cognito_user_pools'" % (auth_type,))
swagger_snippet = {
'in': 'header',
'type': 'apiKey',
Expand Down Expand Up @@ -94,6 +124,8 @@ def _generate_route_method(self, view):
current['security'] = [{'api_key': []}]
if view.authorizer_name:
current['security'] = [{view.authorizer_name: []}]
if view.authorizer:
current['security'] = [{view.authorizer.name: []}]
return current

def _generate_precanned_responses(self):
Expand Down
Loading