Как сделать функцию декоратора python в Flask с аргументами (для авторизации)
Я использовал флеш-фрагмент для моего флеш-входа, который проверяет, что пользователь вошел в систему:
from functools import wraps
def logged_in(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if session.get('logged_in') is not None:
return f(*args, **kwargs)
else:
flash('Please log in first.', 'error')
return redirect(url_for('login'))
return decorated_function
И я украшаю такие взгляды:
@app.route('/secrets', methods=['GET', 'POST'])
@logged_in
def secrets():
error = None
Я хотел бы сделать что-то подобное для авторизации. Сейчас у меня много просмотров, чтобы проверить, что пользователь владеет ресурсом, скажем, ресурсом hotdogs
.
Если пользователь logged_in является владельцем этого конкретного хот-дога, он может редактировать и управлять своими хот-догами. Если нет, я выталкиваю его на несанкционированный экран.
@app.route('/<hotdog>/addmustard/',methods=["GET"])
@logged_in
def addmustard(hotdog):
if not (authorizeowner(hotdog)):
return redirect(url_for('unauthorized'))
do_stuff()
authorizeowner()
принимает хот-дог как ввод и проверяет, что зарегистрированный владелец хот-дога совпадает с именем владельца, указанным в переменной сеанса.
Я попытался создать функцию-оберщик/декоратор owns_hotdog, аналогичный моей регистрации в одном, но он жаловался, что не принимает аргументы. Как я могу достичь чего-то подобного? Что-то вроде...
def owns_hotdog(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not authorizeowner(hotdog):
return f(*args, **kwargs)
else:
flash('Please log in first.', 'error')
return redirect(url_for('login'))
return decorated_function
Из сообщения об ошибке, декоратор, похоже, не получает аргумент hotdog, доступ к которому имеет вид Flask из переменной в маршруте. Я надеюсь на что-то вроде...
@app.route('/<hotdog>/addmustard/',methods=["GET"])
@logged_in
@owns_hotdog(hotdog)
def addmustard(hotdog):
do_stuff()
Все работает с моей текущей функцией authorizeowner (hotdog), но просто кажется, что это чище иметь это место как обертка поверх моего маршрута, а не как первая строка внутри маршрута.
Некоторые другие примечания:
- Я знаю, что Flask-Security и Flask-Principal могут управлять
разрешение для меня. К сожалению, я использую неподдерживаемый
базы данных и я не могу использовать эти расширения. Так что я
вынуждены выполнять аутентификацию без них.
- Если вы видите какие-либо яркие дыры при авторизации таким образом, пожалуйста, дайте мне знать!
Ответы
Ответ 1
Вот как это сделать:
from functools import update_wrapper
def owns_hotdog(hotdog):
def decorator(fn):
def wrapped_function(*args, **kwargs):
# First check if user is authenticated.
if not logged_in():
return redirect(url_for('login'))
# For authorization error it is better to return status code 403
# and handle it in errorhandler separately, because the user could
# be already authenticated, but lack the privileges.
if not authorizeowner(hotdog):
abort(403)
return fn(*args, **kwargs)
return update_wrapper(wrapped_function, fn)
return decorator
@app.errorhandler(403)
def forbidden_403(exception):
return 'No hotdogs for you!', 403
Когда декоратор принимает аргументы, это не действительно декоратор, а функция factory, которая возвращает реальный декоратор.
Но если бы я был вами, я бы использовал Flask-Login для аутентификации и увеличил его с помощью пользовательских декораторов и функций, как ваш, чтобы обрабатывать авторизацию.
Я заглянул в Flask-Principal, но нашел это слишком сложным для моих вкусов. Не проверял Flask-Security, но я считаю, что для авторизации использует Flask-Principal. В целом я думаю, что Flask-Login с некоторым пользовательским кодом достаточно времени.