From 6e99be8b67e516433cb8b2179c5f9b1f7151b4da Mon Sep 17 00:00:00 2001 From: Andrew Standley Date: Wed, 25 Sep 2019 13:42:02 -0700 Subject: [PATCH] Added combined authentication examples to the recipes doc. --- docs/recipes.rst | 126 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/docs/recipes.rst b/docs/recipes.rst index ef4f8976..2faa0dc5 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -60,3 +60,129 @@ Here is a simple recipe for using Flask-Rebar with these pluggable views: This isn't a super slick, classed based interface for Flask-Rebar, but it *is* a way to use unadulterated Flask views to their full intent with minimal `DRY `_ violations. + + +Combining Security/Authentication +================================= + +Authentication is hard, and complicated. Flask-Rebar supports custom Authenticator classes so that you can make +your authentication as complicated as your heart desires. + +Sometime though you want to combine security requirements. +Maybe an endpoint should allow either an admin user or a user with an "edit" permission, +maybe you want to allow requests to use Auth0 or an Api Key, +maybe you want to only authenticate if it's Sunday and Jupiter is in retrograde? + +Here are some simple recipes for what Flask-Rebar currently supports: + + +Allow a user with either scope "A" OR scope "B" + +.. code-block:: python + + from flask import g + from my_app import authenticator, registry + from my_app.scheme import EditStuffSchema, StuffSchema + + + # Allow a user with the "admin" scope OR the "edit:stuff" scope + @registry.handles( + rule="/stuff//", + method="POST",e + request_body_schema=EditStuffSchema(), + response_body_schema=StuffSchema(), + authenticators=[authenticator.with_scope("admin"), authenticator.with_scope("edit:stuff")] + ) + def edit_stuff(thing): + update_stuff(thing, g.validated_body) + return thing + + +Allow a request with either valid Auth0 OR an API-Key + +.. code-block:: python + + from flask import g + from flask_rebar.authenticators import HeaderApiKeyAuthenticator + from flask_rebar_auth0 import get_authenticated_user + from my_app import authenticator, registry + + + # Allow Auth0 or API Key + @registry.handles( + rule="/rate_limit/", + method="GET", + response_body_schema=RateLimitSchema(), + authenticators=[authenticator, HeaderApiKeyAuthenticator("X-API-KEY")] + ) + def get_limits(): + requester = g.authenticated_app_name or get_authenticated_user() + rate_limit = get_limits_for_app_or_user(requester) + return rate_limit + + +Allow a request with Auth0 AND an API-Key + +.. note:: + This currently requires some workarounds. Better support is planned. + +.. code-block:: python + + from flask_rebar.authenticators import HeaderApiKeyAuthenticator + from flask_rebar_auth0 import get_authenticated_user, Auth0Authenticator + from my_app import authenticator + from flask_rebar.swagger_generation.authenticator_to_swagger import ( + AuthenticatorConverter, authenticator_converter_registry + ) + + + class CombindedAuthenticator(Auth0Authenticator, HeaderApiKeyAuthenticator): + + def __init__(app, header): + Auth0Authenticator.__init__(self, app) + HeaderApiKeyAuthenticator.__init__(self, header) + + def authenticate(self): + authenticator.authenticate(self) + HeaderAPIKeyAuthenticator.authenticate(self) + + + auth0_converter = authenticator_converter_registry._get_converter_for_type(authenticator) + header_api_converter = authenticator_converter_registry._get_converter_for_type(HeaderApiKeyAuthenticator("header")) + + class CombinedAuthenticatorConverter(AuthenticatorConverter): + + AUTHENTICATOR_TYPE = CombindedAuthenticator + + def get_security_schemes(self, obj, context): + definition = dict() + definition.update(auth0_converter.get_security_schemes(obj, context)) + definition.update(header_api_converter.get_security_schemes(obj, context)) + return definition + + def get_security_requirements(self, obj, context): + auth_requirement = auth0_converter.get_security_requirements(obj, context)[0] + header_requirement = header_api_converter.get_security_requirements(obj, context)[0] + combined_requirement = dict() + combined_requirement.update(auth_requirement) + combined_requirement.update(header_requirement) + + return [ + combined_requirement + ] + + + authenticator_converter_registry.register_type(CombinedAuthenticatorConverter) + + + @registry.handles( + rule="/user/me/api_token", + method="GET", + authenticators=CombinedAuthenticatorConverter(app, "X-API-Key") + ) + def check_token(): + return 200 + + + +