简体   繁体   中英

what's the correct way to find out flask's endpoint by function name?

I have a flask demo, and it needs access control. My access control is base on function name, and I wrote a decorator to do it.

The decorator define is:

def permission(fn):
    @wraps(fn)
    def wraped(*args, **kwargs):
        if _do_validate(fn.__name__):  # TODO do check user has privileges here
            return fn(*args, **kwargs)
        else:
            abort(401)
    return wraped

Usage example as follow:

    @app.route('/index')
    @permission
    def index():
        return 'hello world'

This work fine if user don't specify a endpoint, because Flask's default endpoint is fn.__name__ , in my example the endpoint='index' .

But when user specify a endpoint, or just use the blueprint, the endpoint was changed. For example:

    bl = Blueprint('admin', __name__)

    @bl.route('hello')
    @permission
    def hello():
        return 'hello world in blueprint'

The endpoint is changed to admin.hello .

I don't want to specify any arg in @permission , so a I write a new permission decorator as follow:

def permission(fn):
    @wraps(fn)
    def wraped(*args, **kwargs):
        m = fn.__module__.split('.')
        if len(m) <= 2:
            # app must define in the root module, so if user not use blueprint, juse use fn.__name__ as endpoint
            reg_f = fn.__name__
        else:
            # blue print must define in the submodule
            reg_f = '{0}.{1}'.format(m[1], fn.__name__)

        if _do_validate(ref_f):  # TODO do check user has privileges here
            return fn(*args, **kwargs)
        else:
            abort(401)
    return wraped

The problem is solved, but I think it's not elegant.

Can anyone give me a better one? thx.

Your solution is not valid in case of blueprint's name is not equal to module's __name__ .

I can propose the next solution:

from collections import defaultdict
registered = defaultdict(int)

def permission(fn):
    registered[fn.__name__] += 1
    fn.permission_token = "%s.%s" % (fn.__name__, registered[fn.__name__])

    @wraps(fn)
    def wraped(*args, **kwargs):
        if _do_validate(fn.permission_token):
            return fn(*args, **kwargs)
        else:
            abort(401)
    return wraped

@permission
def foo():
    pass

@permission
def bar():
    pass

def _do_validate(permission_token):
    return {foo.permission_token: True,
            bar.permission_token: False}.get(permission_token, False)

The main drawback of it is that you have to import all your routes in the _do_validate 's module to create such "access control list".

UPD: Ok, because of you want to use endpoint values to check user access, the next solution allows to find endpoint by view function:

from collections import defaultdict
from flask import current_app

def find_endpoint(fn):
    # This loop can be optimized by maintaining
    # {permission_token => endpoint} dict.
    for endpoint, view_func in current_app.view_functions.items():
        if getattr(view_func, 'permission_token', None) == fn.permission_token:
            return endpoint
    return None

registered = defaultdict(int)

def permission(fn):
    registered[fn.__name__] += 1

    @wraps(fn)
    def wrapped(*args, **kwargs):
        endpoint = find_endpoint(wrapped)
        assert endpoint is not None
        if _do_validate(endpoint):
            return fn(*args, **kwargs)
        else:
            abort(401)

    # This will work because flask's route decorator returns
    # an original view function, not the wrapped one.
    wrapped.permission_token = "%s.%s" % (fn.__name__, registered[fn.__name__])
    return wrapped

def _do_validate(endpoint):
    # Or search in the DB
    return {'index': True,
            'bp.index': False}.get(endpoint, False)

However, checking access by endpoint is not so good idea, because any code change during a development process could lead to undesired losing (or even worse - retrieving!) an access. So, for now my opinion is that using an extra action (or resource ) argument for the permission decorator will be reasonable. With such parameter you can freely modify your endpoints/view functions without any undesired side effects.

UPD2: Actually, the much more simple way to find an endpoint value into a wrapper function is just use request.endpoint .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM