Comment faire une fonction de décorateur python dans Flask avec des arguments (pour l'autorisation)

J'ai utilisé un extrait de flask pour mon Flask-login qui vérifie qu'un utilisateur est connecté:

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

Et je décore des vues comme ceci:

@app.route('/secrets', methods=['GET', 'POST'])
@logged_in
def secrets():
    error = None

Je voudrais faire quelque chose de similaire pour l'autorisation, trop. En ce moment, j'ai beaucoup de vues pour vérifier qu'un utilisateur possède une ressource, disons la ressource hotdogs.

Si l'utilisateur logged_in est le propriétaire de ce hotdog particulier, il peut modifier et gérer ses hotdogs. S'il ne l'est pas, je le vire au non autorisé écran.

@app.route('/<hotdog>/addmustard/',methods=["GET"])
@logged_in
def addmustard(hotdog):
    if not (authorizeowner(hotdog)):
        return redirect(url_for('unauthorized'))
    do_stuff()

authorizeowner() prend un hotdog en entrée et vérifie que le propriétaire du hotdog enregistré correspond au nom du propriétaire répertorié dans la variable de session.

J'ai essayé de créer une fonction owns_hotdog wrapper / decorator similaire à ma fonction connectée, mais il s'est plaint de ne pas accepter les arguments. Comment puis-je réaliser quelque chose de similaire? Quelque chose comme...

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

À partir du message d'erreur, decorator semble ne pas recevoir l'argument hotdog auquel les vues Flask ont accès depuis le variable dans l'itinéraire. Mon espoir est pour quelque chose comme...

@app.route('/<hotdog>/addmustard/',methods=["GET"])
@logged_in
@owns_hotdog(hotdog)
def addmustard(hotdog):
    do_stuff()

Tout fonctionne avec ma fonction actuelle authorizeowner (hotdog), mais il semble juste plus propre d'avoir ceci en place comme un wrapper au-dessus de mon itinéraire, plutôt que comme la première ligne à l'intérieur de l'itinéraire.

Quelques autres notes:

  • Je sais que Flask-Security et Flask-Principal peuvent gérer autorisation pour moi. Malheureusement, j'utilise un non pris en charge back-end de base de données et je ne peux pas utiliser ces extensions. Donc, je suis forcé de faire de l'authentification sans eux.
  • Si vous voyez des trous flagrants en faisant l'autorisation de cette façon, faites-le moi savoir!
23
demandé sur Mittenchops 2012-12-16 02:06:57

1 réponses

Voici comment le faire:

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

Lorsque décorateur prend comme arguments, ce n'est pas vraiment un décorateur, mais un usine la fonction qui retourne le réel décorateur.

Mais si j'étais vous, j'utiliserais Flask-Login pour l'authentification et l'augmenterais avec des décorateurs personnalisés et des fonctions comme le vôtre pour gérer l'autorisation.

J'ai regardé dans Flask-Principal, mais je l'ai trouvé trop compliqué pour mes goûts. Je n'ai pas vérifié Flask-Security, mais je crois qu'il utilise Flacon Principal pour l'autorisation. Dans l'ensemble, je pense que Flask-Login avec un code personnalisé est suffisant la plupart du temps.

20
répondu Audrius Kažukauskas 2012-12-16 09:56:24