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)
Related
Before anything I would like to warn you about my extremely limited knowledge on the subject.
Now that you've been warned, what I need to ask is how can I verify and azureAD access token with Django/django-rest-framework.
I have a app that I need to sign in to with azureAD, which means I need to get an access token from azureAD, and thankfully this is will be done on the frontend side with Angular what I need to do is verify that token on the backend side with django/django-rest-framework and I have no idea where to start from, can anyone suggest a way to do this or send me in the right direction ?
Thank you very much.
Ok, so I owe this to my past self.
What I did is got the authorization from the headers of the request, sliced it so it's the token only and used the used the microsoft keys url, audience and jwt package to decode it, here is the code:
url = "https://login.microsoftonline.com/5c9b264f-33ad-4093-bb65-8d14aaec9f63/discovery/v2.0/keys"
valid_audience = 'api://0d44e6da-8e5b-4e98-94b5-5f02ce228647'
response = urlopen(url)
jwks = json.loads(response.read())
def is_logged_in(func):
def wrapper(self, *args, **kwargs):
token = self.headers['Authorization'][7:] # Need to test this against the frontend request.
try:
jwks_client = PyJWKClient(url)
signing_key = jwks_client.get_signing_key_from_jwt(token)
decoded = jwt.decode(token,
signing_key.key,
algorithms=["RS256"],
audience=valid_audience)
return func(self, *args, **kwargs)
except Exception as e:
print(e)
return HttpResponse('Unauthorized', status=401)
wrapper.__name__ = func.__name__
return wrapper
Also I put it all in a in a decorator so I can hide all my apis behind it.
Is it possible to send data while to redirecting to another view?
For example:-
def social_user():
//do something here
return redirect('name of the view',{'variable':'value'})
or any other alternative to exchange data between a function and view function.
There are two ways to send data to the resulting destination, but first you should understand how redirects work.
When the server redirects, it just sets the Location header telling the browser which page to fetch next.
Location: /foo.html
Since the body is completely ignored even if present, you must somehow pass data via the HTTP headers.
1) Query Parameters
The easiest way is to use query (GET) parameters which you can then read from the destination page:
Location: /foo.html?username=bob
The downside is that the user can easily see and modify this value.
2) Cookies
The session, which is determined by cookies, is another place to store temporary data. But this has several downsides:
It is not tied to any request, so you all consequent request will have access to the data until you delete it.
It doesn't work if the user has disabled cookies. On the other hand query parameters always work.
If you choose the first option, you may need to write some of your own code since Django doesn't seem to offer a convenient method to do this (someone correct me if I'm wrong). Here's a sample function that wraps the functionality:
def redirect_with_query(path, query=None, *args, **kwargs):
if query is None:
query = {}
url = resolve_url(path, *args, **kwargs)
if len(query):
q_dict = QueryDict(mutable=True)
q_dict.update(query)
url += '?' + q_dict.urlencode()
return HttpResponseRedirect(url)
First Solution::
If you have access to the request variable in social_user, you can simply make use of sessions. Sessions are the best way to transfer data between 2 views.
def social_user(request):
request.session['variable'] = 'value'
return redirect('name of the view')
Then inside your view, you can access this variable using request.session.get('variable')
Second Solution::
In case you can't set session variables, send them as query parameters.
def social_user():
//do something here
return redirect("your_view/?variable=value")
Build a "mock django request" manually and call the view directly
Use query parameters
Use render() and pass the variable via context:
def social_user(request)
// do something here
return render(request, template_name='template.html', context={'variable': 'value'})
I am trying to catch the Exception which is raised when the url provided is a messy and wrong url and then return the error response as JSON. This is what i did to implement this logic.
The exception is raised inside the Analysis class when the key_id is not a valid key for S3.
def url_error(status_code, message, reason):
response = jsonify({
'status': status_code,
'message': message,
'reason': reason
})
response.status_code = status_code
return response
class RowColumnCount(Resource):
def get(self, key_id):
try:
rc = Analysis(key_id=key_id)
except S3ResponseError as e:
return url_error(e.status, e.message, e.reason)
json_response = json.loads(rc.count_rows_columns())
return json_response
The above code works fine but its kinda getting repetitive for 50 different Resource classes. Each Resource class should handle this specific error. How to make it a decorator, such that code repetitiveness is reduced.
I am using Flask, Flask-Restful, Python 3.4.3
There are a couple of ways you can achieve what you're trying to do but I think the cleanest way is to extend the Resource class as described in the Flask-Restful docs here and create a new decorator whose job is to catch the S3ResponseError and return the appropriate response. You can then subclass all your resources from your new base Resource class.
Also I would suggest you specify an API level json_output method as described here and here so that way all you have to do is return a dict from any of your resources and they'll be converted to JSON appropriately.
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))
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