From 8cc63164d2ef085ec549a2421668e33763c08337 Mon Sep 17 00:00:00 2001 From: Bryan Strassner Date: Fri, 10 Nov 2017 18:21:13 -0600 Subject: [PATCH] Allow anonymous access for health and versions Updates the context middleware to check for authentication upon routing the request instead of pre-routing. Adds a list to the base resource that means all requests must be authenticated by default, but allows for child resources to specify the HTTP methods that should not be authenticated. Change-Id: I369087430b403355ce551384b8d798465738b684 --- deckhand/control/base.py | 9 ++++++++ deckhand/control/health.py | 7 ++++++- deckhand/control/middleware.py | 38 ++++++++++++++++++++++------------ deckhand/control/versions.py | 6 ++++++ 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/deckhand/control/base.py b/deckhand/control/base.py index 111fe25f..7cde446a 100644 --- a/deckhand/control/base.py +++ b/deckhand/control/base.py @@ -20,6 +20,15 @@ from deckhand import context class BaseResource(object): """Base resource class for implementing API resources.""" + # Shadowing no_authentication_methods and supplying the HTTP method as a + # value (e.g. 'GET') allows that method to run without authentication. By + # default all require authentication. + # Warning: This method of skipping authentication is applied to a HTTP + # method, which ultimately maps to a resource's on_ methods. + # If a method such as on_get were to service both a list and a single + # response, both would share the skipped authentication. + no_authentication_methods = [] + def on_options(self, req, resp): self_attrs = dir(self) diff --git a/deckhand/control/health.py b/deckhand/control/health.py index 12b791c2..7e92f1e9 100644 --- a/deckhand/control/health.py +++ b/deckhand/control/health.py @@ -18,9 +18,14 @@ from deckhand.control.base import BaseResource class HealthResource(BaseResource): - """A resource that allows other UCP components to access and validate + """Basic health check for Deckhand + + A resource that allows other UCP components to access and validate Deckhand's health status. The response must be returned within 30 seconds for Deckhand to be deemed "healthy". + Unauthenticated GET. """ + no_authentication_methods = ['GET'] + def on_get(self, req, resp): resp.status = falcon.HTTP_204 diff --git a/deckhand/control/middleware.py b/deckhand/control/middleware.py index 8faeace9..b9e9c260 100644 --- a/deckhand/control/middleware.py +++ b/deckhand/control/middleware.py @@ -29,25 +29,37 @@ LOG = logging.getLogger(__name__) class ContextMiddleware(object): - def process_request(self, req, resp): - """Convert authentication information into a request context. + def process_resource(self, req, resp, resource, params): + """Handle the authentication needs of the routed request. - Generate a ``deckhand.context.RequestContext`` object from the - available authentication headers and store in the ``context`` attribute - of the ``req`` object. - - :param req: ``falcon`` request object that will be given the context - object. + :param req: ``falcon`` request object that will be examined for method + :param resource: ``falcon`` resource class that will be examined for + authentication needs by looking at the no_authentication_methods + list of http methods. By default, this will assume that all + requests need authentication unless noted in this array. + Note that this does not bypass any authorization checks, which will + fail if the user is not authenticated. :raises: falcon.HTTPUnauthorized: when value of the 'X-Identity-Status' header is not 'Confirmed' and anonymous access is disallowed. """ - if req.headers.get('X-IDENTITY-STATUS') == 'Confirmed': - req.context = deckhand.context.RequestContext.from_environ(req.env) - elif CONF.allow_anonymous_access: - req.context = deckhand.context.get_context() + authentication_required = True + try: + if req.method in resource.no_authentication_methods: + authentication_required = False + except AttributeError: + # assume that authentication is required. + pass + if authentication_required: + if req.headers.get('X-IDENTITY-STATUS') == 'Confirmed': + req.context = deckhand.context.RequestContext.from_environ( + req.env) + elif CONF.allow_anonymous_access: + req.context = deckhand.context.get_context() + else: + raise falcon.HTTPUnauthorized() else: - raise falcon.HTTPUnauthorized() + req.context = deckhand.context.RequestContext.from_environ(req.env) class HookableMiddlewareMixin(object): diff --git a/deckhand/control/versions.py b/deckhand/control/versions.py index 51c986d3..f64cdb61 100644 --- a/deckhand/control/versions.py +++ b/deckhand/control/versions.py @@ -18,6 +18,12 @@ from deckhand.control import base as api_base class VersionsResource(api_base.BaseResource): + """Versions resource + + Returns the list of supported versions of the Deckhand API. + Unauthenticated GET. + """ + no_authentication_methods = ['GET'] def on_get(self, req, resp): resp.body = {