Добавление декораторов auth в flask restx
У меня есть приложение Flask, использующее flask-restx
а также flask-login
. Я бы хотел, чтобы все маршруты по умолчанию требовали входа в систему и явно определяли общедоступные маршруты, не требующие аутентификации. Я начал использовать декораторы, следуя примеру, приведенному в этом вопросе:
Лучший способ сделать значение login_required во Flask-Login по умолчанию
Он работает для конечных точек функций, но не для restx
конечные точки ресурсов.
Я пробовал добавить функцию как декоратор, так и с помощью method_decorators
поле. Например:
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'}
И этот тест проходит:
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
Моя проблема в том, что я не вижу, как получить доступ к этому атрибуту в моей логике авторизации:
@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"))
Это работает для конечных точек простых функций, но не для ресурсов restx.
Я это понимаю restx
оборачивает мой класс ресурсов в функцию, чтобы фляга могла выполнять отправку, но я не могу понять, как получить доступ к декоратору отсюда. Итак, у меня есть вопросы:
- Можно ли добраться до декоратора из view_function?
- Можно ли узнать, является ли конечная точка ресурсом restx или простой функцией отдыха?
- Есть ли лучший способ сделать то, что я пытаюсь достичь?
3 ответа
Исходя из этого и этого,method_decorators
Переменная должна быть списком функций, поэтому вы должны использовать ее так:
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]
Можно ли добраться до декоратора из view_function?
Что ж... это возможно, но я бы не рекомендовал это. Вот пример
Можно ли узнать, является ли конечная точка ресурсом restx или простой функцией отдыха?
Вы, вероятно, можете проверить func и выяснить, не от restx, возможно, глядя на __qualname__
, но опять же, я бы не рекомендовал это.
Есть ли лучший способ сделать то, что я пытаюсь достичь?
Я бы выбрал одно из этих решений:
- Явно украшайте view_funcs и ресурсы, которые нуждаются в аутентификации, а не наоборот
- Создайте схему общедоступных конечных точек, схему защищенных конечных точек с помощью
before_request
декоратор для авторизации
У меня был аналогичный вариант использования. Я хотел применить декоратор к определенным конечным точкам (здесь все маршруты, кроме методов get), используя декоратор по умолчанию flask-restx.
Это мое решение.
def check_role_except_get(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
if func.__name__ == "get"
return func(*args,**kwargs)
else:
........ Authorization Logic Here / Abort if Authorization fails----
return wrapper
def authorization(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
func.view_class.method_decorators = [check_role_except_get]
return func(*args,**kwargs)
return wrapper
api = Api(app, version='1.0', title='My API',
description='A simple API',decorators=[authorization]
)
ns = api.namespace('todos', description='TODO operations')
@ns.route('/')
class TodoList(Resource):
'''Shows a list of all todos, and lets you POST to add new tasks'''
def get(self):
'''List all tasks'''
return "resp"
def post(self):
'''Create a new task'''
return resp