Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding auth decorators to flask restx

I have a Flask application using flask-restx and flask-login. I would like all routes by default to require login, and explicitly define public routes that require no authentication. I have started using decorators following the example given in this question:

Best way to make Flask-Login's login_required the default

It works for function endpoints, but not for restx resource endpoints.

I have tried adding the function both as a decorator, and using the method_decorators field. For example:

def public_route(decorated_function):
    """
    This is a decorator to specify public endpoints in our flask routes
    :param decorated_function:
    :return:
    """
    decorated_function.is_public = True
    return decorated_function


class HelloWorld(ConfigurableResource):

    method_decorators = {"get": [public_route]}

    @public_route
    @api.doc('Welcome message')
    def get(self):
        return {'hello': 'world'}

And this test passes:

def test_hello_world_is_public():
    api = Namespace('health', description='Health related operations')
    hello = HelloWorld(api, config=None, logger=None)
    is_public_endpoint = getattr(hello.get, 'is_public', False)
    assert is_public_endpoint

My challenge is I can't see how to access this attribute in my auth logic:


    @app.before_request
    def check_route_access():
        """
        This function decides whethere access should be granted to an endpoint.
        This function runs before all requests.
        :return:
        """
        is_public_endpoint = getattr(app.view_functions[request.endpoint], 'is_public', False)

        if person_authorized_for_path(current_user, request.path, is_public_endpoint):
            return
        # Otherwise access not granted
        return redirect(url_for("auth.index"))

This works for plain function endpoints, but not restx resources.

I understand that restx is wrapping my resource class in a function so that flask can do dispatch, but I can't figure out how to access the decorator from here. So I have some questions:

  • Is it possible to reach the decorator from the view_function?
  • Is it possible to know whether the endpoint is a restx resource or a plain rest function?
  • Is there a better way to do what I'm trying to achieve?
like image 594
mikelong Avatar asked Jul 31 '20 08:07

mikelong


2 Answers

Based on this and this, method_decorators variable should be a list of functions, so you should use it like:

def _perform_auth(method):
    is_public_endpoint = getattr(method, 'is_public', False)
    # place the validation here

class Resource(flask_restx.Resource):
    method_decorators = [_perform_auth]
like image 98
Andrey Kurilin Avatar answered Sep 18 '22 14:09

Andrey Kurilin


Is it possible to reach the decorator from the view_function?

Well... it is possible, but I wouldn't recommend it. Here's an example

Is it possible to know whether the endpoint is a restx resource or a plain rest function?

You probably can inspect the func and figure out if it's from restx, maybe looking at __qualname__, but then again, I`d wouldn't recommend it.

Is there a better way to do what I'm trying to achieve?

I would go one of these solutions:

  • Explicitly decorate view_funcs and resources that do need authentication, instead of the other way around
  • Create a blueprint for public endpoints, a blueprint for protected endpoints with the before_request decorator for authorization
like image 39
Gabriel Cappelli Avatar answered Sep 20 '22 14:09

Gabriel Cappelli