Passing a cause description to a 403 handler view - django

I want to show an informative error message to a user hitting a page that results in a 403 Forbidden. I'm using Django 1.8.
My idea of passing an error message is this:
# in a view
if not ...:
raise PermissionDenied('You have no power here')
Then somewhere in the handler403 view, I'd like to retrieve the message (or maybe even a structured object) and render an informative page based on it.
Though the handler seems to only receive the request object and the template name.
Is there a way to pass a custom message to the handler? (Of course I could assign a custom attribute to the request object before raising the exception, and hope that the same object will be passed to the handler, but this feels like a hack.)
Update: Indeed there is a middleware for that already: contrib/messages is capable of piggybacking messages on the request object, and more.

Looks like you would have to implement this using a middleware:
class ExceptionMiddleware:
def process_exception(self, request, exception):
if exception.args:
request.message = exception.args[0]
Then in your handler403 you could access request.message.
Obviously you could flesh-out this middleware to allow passing more than just a message to the view.

Related

Django to catch all invalid logins and unauthorized access like 401s and 403s

I want to capture all invalid logins/unauthorized access such as 401s and 403s returned from the site so I can log them to a security logging service, investigating if there is an easy way to catch all of these without putting in much custom logic.
I have tried using middleware approach:
def simple_middleware(get_response):
# One-time configuration and initialization.
def middleware(request):
response = get_response(request)
if response.status_code in [403, 401]:
log.warning('invalid login')
return response
return middleware
Unfortunately an incorrect login to the /admin/ login, it returns status 200, however I think this would work for custom login that explicitly throws 401/403.
I have also tried using the signal approach using request_finished but all I get is just the handler class.
So... looking for ideas.
As you found out, a login attempt doesn't necessarily imply a specific response code, since you may decide to treat the attempt with a redirect or any other type of answer.
In case of Django, the default auth middleware (which I assume you are using) fires a user_login_failed signal which you can handle with your logging logic.
You can see in the documentation how to register a signal handler, so it should be something like
from django.contrib.auth.signals import user_login_failed
from django.dispatch import receiver
#receiver(request_finished)
def handle_login_failed(sender, **kwargs):
print(f"Oops, login failed using these credentials: {kwargs.get('credentials', None)}")
The signal triggering is in the source code for the auth package.

Exception handling for missing parameters of a view passed on as json data

Lately, I found out the elementary truth related to exception handling - our server should rarely (preferably, never) through 5XX error.
Having said that, let's consider a REST view with some parameters, and a respective URL.
urlpatterns = [
url(r'^some_view/', some_view),
]
#api_view(['POST'])
def some_view(request):
# ...
param1 = request.data['param1']
In this code, I have to manually handle the exception where the developer calls some_view without assigning value to param1, otherwise - if I don't handle this case explicitly - they will get 500 error (MultipleKeyValueError) which is bad. And we have a dilemma:
Handling it will cause irritating and repetitive try-except blocks, especially when we have multiple params
Not handling will lead to 500 error
The solution is to rewrite the view this way:
urlpatterns = [
url(r'^some_view/(?P<param1>\w+/', some_view),
]
#api_view(['POST'])
def some_view(request, param1):
# ...
Here Django will throw 400 exception - as opposed to 500 - saying that url is not found. But on the other hand, the first option (where I use request.data['param1']) gives the pleasant benefit that I can call the REST resource not only from an outside app, but also from my web app submitting params by serializing HTML form.
So here I am asking about the best practice. How do you, guys, handle this situation? Do you explicitly write try-except blocks watching for missing params, or do you use the url-parameters options, or maybe there's some third option that I didn't mention here ?
If you're writing REST views, you should be using Django Rest Framework. One of the benefits of that framework is that you define serializers in which you declare which fields you accept and which are mandatory, just as you do with forms in vanilla Django. You can therefore return validation errors rather than 500s.
So here I am asking about the best practice. How do you, guys, handle
this situation? Do you explicitly write try-except blocks watching for
missing params, or do you use the url-parameters options, or maybe >there's some third option that I didn't mention here ?
If I am trying to handle a common pattern for exception handling across views, I typically us a “third option” of writing a decorator that handles the exception and returns a JSON response with success = False:
from functools import wraps
def handle_missing_key(func)
#wraps(func)
def _decorator(*args, **kwargs):
try:
func(*args, **kwargs)
except KeyError as ex:
return JsonResponse({
'success': False,
'error': '%s' % ex
})
return _decorator
Then for your methods you can just do:
#handle_missing_key
#api_view(['POST'])
def some_view(request, param1):
pass
This way you're just sending back a 200 response but the JSON defines the error message and lets any calling application know what is missing. You can find the missing key using KeyError.args or the error message in the JsonResponse. If you want to send back a response other than a JsonResponse (i.e., status response 400 or the like), you can, of course, use something like this:
response = HttpResponse(status=400)
response.reason_phrase = 'Key %s is missing' % ex
return response

Django app treats HTTP request as HttpRequest object, not django-rest Request object

I have the following code
class UserPollPassedView(generics.UpdateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UsersPollUpdateSerializer
def update(self, request, *args, **kwargs):
request.data['date_passed'] = datetime.datetime.now()
request.data['passed'] = True
return super(UserPollPassedView, self).update(request, *args, **kwargs)
When I try to access this method through such curl instrument like hurl.it, I get QueryDict is immutable error, because I'm trying to modify the content of request.data, assuming that request is Django-rest's Request object, but it's not, it is actually native Django HttpRequest object. When I explicitly define Content-type: application\json in request header, it gives me another JSON parse error. When I use httpie tool, everything goes smooth and clean, though it sends the same application\json header by default. I suppose, the difference is still in headers, but I can't understand what exactly I should change.
I also tried explicitly typecast request from HttpRequest to Request by adding the following line right before accessing request.data
request = self.initialize_request(request)
but of no use, it gives same "JSON parse error - Expecting value: line 1 column 1 (char 0)" even if request is initially Request object from httpie.
Would appreciate any clue
I don't know why you think the object is not the DRF request. Since you are using a DRF view, you get a DRF request. Django's own request object doesn't even have a data attribute.
The request variable is definitely holding a DRF Request object, and not a Django HttpRequest object. You would be getting an error about data not existing on the HttpRequest if this was not the case.
The problem is that request.data is a QueryDict object when the data is submitted as form data (like through a browser), which should be close to what you would get from request.POST/request.GET/request.FILES. This is probably why you thought it was a Django HttpRequest, because this is usually an issue people see after trying to modify any of these objects.
You are trying to modify request.data which isn't recommended and should really be treated as an immutable dictionary. Usually this is a sign that you should be setting these keys somewhere else, usually on the serializer layer or by overriding perform_update as a hook), instead of modifying the incoming request data.

Using ParseError Django REST Framework to return invalid data

I have a similar issue as this question about validating data in the Django REST Framework outside of a serializer:
Raise Validation Error In Pre_Save Using Django Rest Framework
My code:
def pre_save(self, obj):
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError('An unpermitted user has been included')
From the trace it looks like it's trying to send the response but it fails with:
"" needs to have a value for field before this many-to-many relationship can be used.
UPDATE:
I moved raising the ParseError into a get_serializer_class() method like so:
def get_serializer_class(self):
if 'users' in self.request.DATA:
# make sure the users are allowed
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError(detail='Unpermitted user')
return serializer
And this raises the exception, however, it does not return it using the REST framework's JSON response. Rather I get the django stack trace and a 500 error, which is not good.
Thanks!
Have a look at APIView's handle_exception — this is where DRF processes exceptions raised during the request.
From the docs:
The default implementation handles any subclass of rest_framework.exceptions.APIException, as well as Django's Http404 and PermissionDenied exceptions, and returns an appropriate error response.
If you need to customize the error responses your API returns you should subclass this method.
So you need to override this to handle ParseError exceptions too.
Also check out the DRF docs on Exceptions.
I hope that helps.
When the exception is raised in the pre_save method(), post_save(), or even in a post() method for the viewclass, it was being handled correctly by Django-REST-Framework. Had I been using curl or similar, the error would have been returned correctly.
This actually is a bug in the browsable API, which is what I was using to test - sending the data using the "Raw data" form. When trying to render the html response, DRF apparently tries to capture the "context" of the post. In this case, it wanted the saved/completed post.
That did not exist, so a Django rendering error was being thrown and that confused me.
When testing using curl, the response was accurate.
Note that putting it in the get_serializer_class() like I did caused it to go outside of the DRF exception handler so Django rendered it correctly and showed the error was being thrown correctly.

redirect to another page instead of recieving Django standarf 404 error

i have something like this in my views.py
instance = get_object_or_404(register,pk=request.user.id)
Now if there is no related object against this user i receive i standard django 404 eror
saying no matches found.
what i want here is instead of receiving this 404 error redirect it to another page say "something.html". but i dont know how. i am using method = "POST"
is there any way to redirect it to other page instead of receiving a 404 error
using a try/except block you can redirect if the object is not found
try:
instance = register.get(pk=request.user.id)
except register.DoesNotExist:
return HttpResponseRedirect('url that renders something.html')
FYI, definition of django get_object_or_404 function looks like this
def get_object_or_404(klass, *args, **kwargs):
"""
Uses get() to return an object, or raises a Http404 exception if the object
does not exist.
klass may be a Model, Manager, or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
Note: Like with get(), an MultipleObjectsReturned will be raised if more than one
object is found.
"""
queryset = _get_queryset(klass)
try:
return queryset.get(*args, **kwargs)
except queryset.model.DoesNotExist:
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
from the docs, If you raise Http404 at any point in a view function, Django will catch it and return the standard error page for your application, along with an HTTP error code 404.
look at customizing error views if you want to render a custom 404.html based on the context variables
Depending on what the Big Picture is, you might want to look at django.contrib.flatpages and see what they are doing. Basically they are dealing with the 404 in middleware and then looking at the path to decided if there is something they can return. I have used variations on this on a couple of sites.