Django: How to raise Http401 and Http403 exceptions like Http404, RAISE EXCEPTION not RESPONSE
Question:
I’m trying to make an api using Django, and i’m verifying the request header if it contains an api key and raise an exception according to that, like so:
def _check_driver_authorization(request):
if request.headers.get('authorization') is not None:
token = request.headers['authorization']
user = Driver.objects.filter(access_token=token)
if user.exists():
return
raise Http401
else:
raise Http403
I didn’t find anyone trying to make it like this, and i’ve searched many threads in here, they are all trying to return responses (render), my case i’m trying to interupt the request and raise the exception.
I inspired this from get_object_or_404.
Edit/update:
for more details and explanation, the built-in Http404 exception raises this:
Http404
but the exception i tried to make (exactly the same as Http404), raises this:
Http403
Answers:
There are handler and exception to response converter which allows you to do a raise Http404
. As you can see they also convert PermissionDenied
exception to response with 403 status code. So you can raise it instead of Http403. But for 401 you have to return return HttpResponse('Unauthorized', status=401)
something like that.
This is the piece of Django code handling your exceptions:
def response_for_exception(request, exc):
if isinstance(exc, Http404):
if settings.DEBUG:
response = debug.technical_404_response(request, exc)
else:
response = get_exception_response(request, get_resolver(get_urlconf()), 404, exc)
elif isinstance(exc, PermissionDenied):
logger.warning(
'Forbidden (Permission denied): %s', request.path,
extra={'status_code': 403, 'request': request},
)
response = get_exception_response(request, get_resolver(get_urlconf()), 403, exc)
elif isinstance(exc, MultiPartParserError):
logger.warning(
'Bad request (Unable to parse request body): %s', request.path,
extra={'status_code': 400, 'request': request},
)
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
elif isinstance(exc, SuspiciousOperation):
if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent)):
# POST data can't be accessed again, otherwise the original
# exception would be raised.
request._mark_post_parse_error()
# The request logger receives events for any problematic request
# The security logger receives events for all SuspiciousOperations
security_logger = logging.getLogger('django.security.%s' % exc.__class__.__name__)
security_logger.error(
force_text(exc),
extra={'status_code': 400, 'request': request},
)
if settings.DEBUG:
response = debug.technical_500_response(request, *sys.exc_info(), status_code=400)
else:
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
elif isinstance(exc, SystemExit):
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise
else:
signals.got_request_exception.send(sender=None, request=request)
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
# Force a TemplateResponse to be rendered.
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
response = response.render()
return response
As you can see here, Django will handle Http404
and PermissionDenied
exceptions. It seems there’s no exception for an Http 401 response.
PermissionDenied
(Http 403) exception is the following one (similar to Http404 exception):
class PermissionDenied(Exception):
"""The user did not have permission to do that"""
pass
So it should work with a raise PermissionDenied
.
Django docs: Error views
If you are using django rest framework, you can inherit from APIException like so:
from rest_framework.exceptions import APIException
class UnauthorizedException(APIException):
status_code = 401
default_detail = "Not logged in"
and then for example in your view or wherever you can do like
if request.user.is_anonymous:
raise UnauthorizedException
The caller then gets a 401.
I’m trying to make an api using Django, and i’m verifying the request header if it contains an api key and raise an exception according to that, like so:
def _check_driver_authorization(request):
if request.headers.get('authorization') is not None:
token = request.headers['authorization']
user = Driver.objects.filter(access_token=token)
if user.exists():
return
raise Http401
else:
raise Http403
I didn’t find anyone trying to make it like this, and i’ve searched many threads in here, they are all trying to return responses (render), my case i’m trying to interupt the request and raise the exception.
I inspired this from get_object_or_404.
Edit/update:
for more details and explanation, the built-in Http404 exception raises this:
Http404
but the exception i tried to make (exactly the same as Http404), raises this:
Http403
There are handler and exception to response converter which allows you to do a raise Http404
. As you can see they also convert PermissionDenied
exception to response with 403 status code. So you can raise it instead of Http403. But for 401 you have to return return HttpResponse('Unauthorized', status=401)
something like that.
This is the piece of Django code handling your exceptions:
def response_for_exception(request, exc):
if isinstance(exc, Http404):
if settings.DEBUG:
response = debug.technical_404_response(request, exc)
else:
response = get_exception_response(request, get_resolver(get_urlconf()), 404, exc)
elif isinstance(exc, PermissionDenied):
logger.warning(
'Forbidden (Permission denied): %s', request.path,
extra={'status_code': 403, 'request': request},
)
response = get_exception_response(request, get_resolver(get_urlconf()), 403, exc)
elif isinstance(exc, MultiPartParserError):
logger.warning(
'Bad request (Unable to parse request body): %s', request.path,
extra={'status_code': 400, 'request': request},
)
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
elif isinstance(exc, SuspiciousOperation):
if isinstance(exc, (RequestDataTooBig, TooManyFieldsSent)):
# POST data can't be accessed again, otherwise the original
# exception would be raised.
request._mark_post_parse_error()
# The request logger receives events for any problematic request
# The security logger receives events for all SuspiciousOperations
security_logger = logging.getLogger('django.security.%s' % exc.__class__.__name__)
security_logger.error(
force_text(exc),
extra={'status_code': 400, 'request': request},
)
if settings.DEBUG:
response = debug.technical_500_response(request, *sys.exc_info(), status_code=400)
else:
response = get_exception_response(request, get_resolver(get_urlconf()), 400, exc)
elif isinstance(exc, SystemExit):
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise
else:
signals.got_request_exception.send(sender=None, request=request)
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
# Force a TemplateResponse to be rendered.
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
response = response.render()
return response
As you can see here, Django will handle Http404
and PermissionDenied
exceptions. It seems there’s no exception for an Http 401 response.
PermissionDenied
(Http 403) exception is the following one (similar to Http404 exception):
class PermissionDenied(Exception):
"""The user did not have permission to do that"""
pass
So it should work with a raise PermissionDenied
.
Django docs: Error views
If you are using django rest framework, you can inherit from APIException like so:
from rest_framework.exceptions import APIException
class UnauthorizedException(APIException):
status_code = 401
default_detail = "Not logged in"
and then for example in your view or wherever you can do like
if request.user.is_anonymous:
raise UnauthorizedException
The caller then gets a 401.