8

I'm building a social website that uses django templates/dynamic pages (no SPA technology in place). I have some ajax calls that check the users news feed or new messages. Example GET web request of those looks as follows:

GET /feeds/check/?last_feed=3&feed_source=all&_=1500749662203 HTTP/1.1

This is how I receive it in the view:

@login_required
@ajax_required
def check(request):
    last_feed  = request.GET.get('last_feed')
    feeds = Feed.get_feeds_after(last_feed)

It all works, but I want to protect it so the function get_feeds_after does not crash when a malicious user sets the GET parameter to last_feed="123malicious4556". Currently it crashes because in the Feed model the function does this:

@staticmethod
def get_feeds_after(feed):
    feeds = Feed.objects.filter(parent=None, id__gt=float(feed))
    return feeds

and crashes with the error:

ValueError at /feeds/check/
invalid literal for float(): 2fff2

I currently solve this by directly performing checks on the GET variable and handling exception on int() casting:

def check(request):
    last_feed  = request.GET.get('last_feed')
    try:
        feed_source = int(request.GET.get('last_feed'))
    except ValueError:
        return HttpResponse(0)

My question is what is the best django-recommended way to address this? I know django has special support forms validation. But this does not seem quite right here, as the GET calls are more of an api rather than forms so it seems like a bad idea to define forms for those GET parameters.

Thanks

Loop Back
  • 81
  • 1
  • 2

3 Answers3

5

All you actually need are the form fields which do all basic validation for you. If you need custom validation you can write your own validator or even better your own custom form field.

To use the field alone without the form you can do like that for example:

evalType = forms.CharField().clean(request.GET.get('eval-type'))

Because calling this way is not very human friendly I prefer to write a function to deal with it:

def cleanParam(params, paramName, FieldType, *args, **kwargs):
    field = FieldType(*args, **kwargs)
    cleaned = field.clean(params.get(paramName))
    return cleaned

Which we use this way:

evalType = cleanParam(request.GET, 'eval-type', forms.CharField)

This will save you a form class. But I don't think it's very ugly to create a django form for that. A bit too much for the problem but no great concern IMHO.

The advantage of having a form is that you declare the fields you expect in your api call and can check all at once then see the result of is_valid().

I hope this helps.

Eric Chiesse
  • 455
  • 3
  • 8
2

Request params can be validated with the DRF serializer.

  1. Create a serializer with all the parameters that are required to be validated.
from rest_framework import serializers

class OrderIDSerializer(serializers.Serializer):
    order_id = serializers.CharField(required=True)

    def validate(self, attrs):

        order_id = attrs.get('order_id')

        if not Order.objects.filter(
            id=order_id).exists():
            raise error
        attrs['order_id'] = order_id

        return attrs
  1. Import serializer in the views:
from order.serializers.order_serializer import OrderIDSerializer

serializer = OrderIDSerializer(data=request.query_params)
if serializer.is_valid():
    order_id = serializer.data['order_id']
   // your logic after the validation
   

Dheeraj
  • 19
  • 2
  • The OP doesn't already seem to be using DRF. Your answer supposes they already know about it (And also where to import the values like `serializers` from). Please [edit] your answer and add that information so that it is a better and more clear answer. :) – Abdul Aziz Barkat Jun 13 '22 at 14:57
1

You can also use a django Validator, which is

a callable that takes a value and raises a ValidationError if it does not meet some criteria.

In your specific case, you can use a combination of validate_slug() - which does not raise a ValidationError only if the passed input is composed of letters, numbers, underscores or hyphens - and int() functions in this way:

from django.core.validators import validate_slug
 ...

last_feed = request.GET.get("last_feed", "")
try:
    validate_slug(last_feed)
    last_feed = int(last_feed)

     ...
except ValueError:
    print("invalid literal passed to int()")
except ValidationError:
    print("invalid input passed to validate_slug()")

 ...
Filippo Lauria
  • 1,965
  • 14
  • 20