Simplish Flask methodview decorator - python

I'm working on a decorator that validates an api token, my first being to get the decorator in some minimal working order:
def check_token(view_method):
#wraps(view_method)
def wrapped_view(*args, **kwargs):
token = request.args['token']
if token is None:
abort(403)
return view_method(*args, **kwargs)
return wrapped_view
This sort of works, i.e. the view works if the token is provided, but if not gives a 400 Bad Request error. I want to send a 403 error, but am not aware enough to catch this yet.
What is wrong with this that it only return a 400 error? How do I improve this?

request.args is a MultiDict and it raises if a key is requested which is not there in dict. Here request.args['token'] raises before it reaches to abort(403). Few solutions are:
## If a 2nd optional argument is provided to dict.get then
## it does not raise if key is not found. Instead returns
## the 2nd argument passed as default.
token = request.args.get('token', None)
if not token:
## Error handling
Or:
if 'token' not in request.args or not request.args['token']
## Error handling

Related

How to use a decorator in Python to perform authentication?

I am implementing a Flask-RESTFul API where I receive a token from postman and compare it with the token I configured in my code as below:
file: functions.py
def authenticate():
headers = flask.request.headers
bearer = headers.get('Authorization')
token = bearer.split()[1]
try:
if str(hashlib.md5(b'Token_String').hexdigest()) == token:
logger.info('Token authentication successful.')
return True
else:
logger.error('Token autherization failed. Please check the token supplied.')
return False
except Exception as e:
if token is None:
logger.info('Token cannot be null. Supply a token with API call.')
return {'message': 'Token cannot be null. Exception: {error}'.format(error=e)}, 400
else:
logger.info('Token cannot be null. Supply a token with API call.')
return {'message': 'Error reading token. Cannot be Null/Empty. Exception: {error}'.format(error=e)}, 400
This is my API's get method:
class APIClass(Resource):
#classmethod
def get(self):
logger.info('Initiating get()')
if fc.authenticate():
run_some_sql_statements
else:
return {'message': 'Token authentication failed'}, 401
pass
Instead of using an IF-Condition, is there a way I can use the method: authenticate from functions.py file as a decorator on top of my get().
I tried doing this and faced the below error:
from validations import functions as fc
#classmethod
#fc.authenticate
def get(self):
But I see a compilation error: Function 'authenticate' lacks a positional argument
Could anyone let me know what is the mistake I made here and how can I correct it ?
The flask-restful docs contain a section about resource method decorators:
https://flask-restful.readthedocs.io/en/latest/extending.html#resource-method-decorators
class APIClass(Resource):
method_decorators = [authenticate]

flask redirecting with url and api key required

I have two endpoints and I'd like to redirect from one to the other. Both require the same api key:
#blueprint.route("v1/method/<param>", methods=["PUT"])
#api_key_required
#write_required(api=True)
def method(param):
return redirect(url_for('v2.method', param=param), code=307)
#blueprint.route("v2/method/<param>", methods=["PUT"])
#api_key_required
#write_required(api=True)
def method(param):
#handle the request
redirection seems to work fine but it looks like api_key is not passed. I receive unauthorised status. What should I do?
I found the solution. request.args need to be pass as additional arguments. So the correct code is:
return redirect(url_for('v2.method', param=param, **request.args), code=307)

how to get access to error message from abort command when using custom error handler

Using a python flask server, I want to be able to throw an http error response with the abort command and use a custom response string and a custom message in the body
#app.errorhandler(400)
def custom400(error):
response = jsonify({'message': error.message})
response.status_code = 404
response.status = 'error.Bad Request'
return response
abort(400,'{"message":"custom error message to appear in body"}')
But the error.message variable comes up as an empty string. I can't seem to find documentation on how to get access to the second variable of the abort function with a custom error handler
If you look at flask/__init__.py you will see that abort is actually imported from werkzeug.exceptions. Looking at the Aborter class, we can see that when called with a numeric code, the particular HTTPException subclass is looked up and called with all of the arguments provided to the Aborter instance. Looking at HTTPException, paying particular attention to lines 85-89 we can see that the second argument passed to HTTPException.__init__ is stored in the description property, as #dirn pointed out.
You can either access the message from the description property:
#app.errorhandler(400)
def custom400(error):
response = jsonify({'message': error.description['message']})
# etc.
abort(400, {'message': 'custom error message to appear in body'})
or just pass the description in by itself:
#app.errorhandler(400)
def custom400(error):
response = jsonify({'message': error.description})
# etc.
abort(400, 'custom error message to appear in body')
People rely on abort() too much. The truth is that there are much better ways to handle errors.
For example, you can write this helper function:
def bad_request(message):
response = jsonify({'message': message})
response.status_code = 400
return response
Then from your view function you can return an error with:
#app.route('/')
def index():
if error_condition:
return bad_request('message that appears in body')
If the error occurs deeper in your call stack in a place where returning a response isn't possible then you can use a custom exception. For example:
class BadRequestError(ValueError):
pass
#app.errorhandler(BadRequestError)
def bad_request_handler(error):
return bad_request(str(error))
Then in the function that needs to issue the error you just raise the exception:
def some_function():
if error_condition:
raise BadRequestError('message that appears in the body')
I hope this helps.
I simply do it like this:
abort(400, description="Required parameter is missing")
flask.abort also accepts flask.Response
abort(make_response(jsonify(message="Error message"), 400))

Web2Py POST method not working according to example :-(

I am making an API for my web2py app and for the life of me cannot get it to work as shown in the web2pybook:
http://www.web2py.com/books/default/chapter/29/10/services#Low-level-API-and-other-recipes
Here is api from the defaults.py:
#request.restful()
def api():
response.view = 'generic.json'
def GET(app_listings,id):
if not app_listings=='app': raise HTTP(400)
return dict(app = db.app_listings(id))
def POST(app_user,device):
if not app_user=='usr': raise HTTP(400)
return db.usr.validate_and_insert(device)
return locals()
And here is my url for testing the API:
http://127.0.0.1:8000/my_app/default/api/usr/3/
My responses are:
400 BAD REQUEST
or if I remove the parameter on the end of the URL:
invalid arguments
Suggestions?
In your URL, you are passing "usr" and "3" as the two arguments to the GET() function, and that function includes the following line:
if not app_listings=='app': raise HTTP(400)
Since "usr" != "app", it raises the HTTP(400) exception.

Adding custom response Headers to APIException

I have created a custom exception referring to http://django-rest-framework.org/api-guide/exceptions.html.
Please know that I have my own authentication backend. Hence I am not using rest_framework's authentication module.
For authentication errors, I want to add 'WWW-Authenticate: Token' header to the response that is sent from the exception.
Any ideas will be very helpful.
Update:
Thanks #Pathétique,
This is what I ended up doing.
-Have a base view class named BaseView.
-override the handle_exception method to set appropriate headers, in my case 'WWW-Authenticate'.
Here is the code:
class BaseView(APIView):
def handle_exception(self, exc):
if isinstance(exc, MYEXCEPTION):
self.headers['WWW-Authenticate'] = "Token"
return Response({'detail': exc.detail,
status=exc.status_code, exception=True)
Your thoughts?
Try overriding finalize_response in your rest framework view:
def finalize_response(self, request, *args, **kwargs):
response = super(SomeAPIView, self).finalize_response(request, *args, **kwargs)
response['WWW-Authenticate'] = 'Token'
return response
Edit:
After seeing your update, I think your override of handle_exception should work, I would only add an else statement to call the parent method to cover other exceptions. One thing I noticed in overriding dispatch, which may not be an issue here, is that setting a new key/value for self.headers resulted in a server error that I didn't take the time to track down. Anyways, it seems you are on the right track.
Use the authenticate_header method on your authentication class.
Additionally that'll ensure your responses also have the right 401 Unauthorized status code set, instead of 403 Forbidden.
See here: http://django-rest-framework.org/api-guide/authentication.html#custom-authentication
Your solution is quite correct, in my case I found it more appropiate to add the header and then call the method on the super instance, to maintain default behaviour:
class BaseView(APIView):
def handle_exception(self, exc):
if isinstance(exc, MYEXCEPTION):
self.headers['WWW-Authenticate'] = "Token"
return super().handle_exception(excepto)

Categories