Why is my decorator breaking for this Flask-Login

2019-08-15 02:09发布

问题:

I've figured out how to make Flask-Login authenticate a user based on an LDAP lookup. Now I'd like to add some authorization to the mix - that is, only allow access to certain endpoints if a user has both logged in and belongs to the right groups.

I'm not sure if this is the right way to do it, but I thought I could just add a decoration to an endpoint:

@app.route('/top_secret')
@authorize
@login_required
def top_secret():
    return render_template("top_secret.html")

and (for now) make a completely pass-through decorator:

def authorize(func):
    def newfunc(*args, **kwargs):
        return func(*args, **kwargs)
    return newfunc

But running this gives the error:

werkzeug.routing.BuildError: Could not build url for endpoint 'top_secret'.

while running it without the @authorize decorator works fine.

So I'm not sure where I went wrong. Can I not decorate endpoints in this way? And if not, where should the authorization step take place? (it would have to take place after @login_required has finished what it needs to do)

回答1:

Ah, yeah, super simple!

from functools import wraps


def authorize(func):
    @wraps(func)
    def newfunc(*args, **kwargs):
        return func(*args, **kwargs)
    return newfunc

To understand why you need wraps, you can do this:

 print(top_secret)

Once with wraps, and once without. With wraps, you'll see something like this:

<function top_secret at 0x7ff165150840>

Without it, you'll see:

<function authorize.<locals>.newfunc at 0x7f81b8456840>

The documentation for wraps is really good, but basically when you wrap a function by default you lose the docstrings, and attributes, and all the rest. So you lose the app.route decoration for your function, so Flask can't figure out how to build the URL.

By adding the wraps decorator on there, you restore everything that app.route did to your function, and Flask can create your route.