python-restx / flask-restx

Fork of Flask-RESTPlus: Fully featured framework for fast, easy and documented API development with Flask
https://flask-restx.readthedocs.io/en/latest/
Other
2.16k stars 335 forks source link

Adding Default Decorator to Specific endpoints ( Eg : All methods except GET ) : User Authorization Usecase #559

Closed jithin-m closed 1 year ago

jithin-m commented 1 year ago

I had to block access to users for all resource-modifying HTTP methods ( post, put, delete, etc.... ). This can be done by decorating authorization checks on all HTTP methods except get methods. But I do not want to go to each resource and add this decorator. (If I accidentally missed adding this decorator this will expose my API to users without write access) So I thought I could make use of the default decorators in the API() object. But the default decorator is resource level and not method decorator.

I finally found a solution. The solution is as follows.

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

This solution works for me, But does it have any impact which I missed? Your feedback is appreciated.

Thanks

peter-doggart commented 1 year ago

If it was me, I would put the logic into flasks app.before_request(), as it is guaranteed that is executed prior to any of your logic on each request. See https://stackoverflow.com/a/52572337 for some inspiration.

You can access the request verb using if request.method == 'GET': etc. So if "PUT" etc, require auth and if invalid, error and return the correct HTTP response. If "GET" then simply return None, and it will continue and execute your endpoint function.

jithin-m commented 1 year ago

@peter-doggart Thanks for suggesting an alternative. I need to first authenticate the user and if it's successful I need to check for Authorization if it's not a get method. So if I go for flask before request method wouldn't it ask for authentication in Swagger view also ?

peter-doggart commented 1 year ago

Yes, but since you can access the request object, you could check if the request was for the swagger endpoint or the static files associated with it, and skip the authentication for those.