Django-filter: Raise an exception if fields are not given in FilterSet

Created on 28 Sep 2016  ·  11Comments  ·  Source: carltongibson/django-filter

class BooksFilter(filters.FilterSet):
    class Meta:
        model = Books

When omitting the fields option in a filterset class (as above), all model fields will be included in the filterset. This may lead to information leakage when used in conjunction with DRF, where the form is rendered in the browsable API.

Both Django and DRF requires fields to be specified explicitly in forms/serializers because of the potential security issues. This should be required in django-filter filtersets as well.
http://www.django-rest-framework.org/api-guide/serializers/#specifying-which-fields-to-include
https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/#selecting-the-fields-to-use

I am happy to contribute with a PR or explain more about in which situations this can lead to information leakage, if needed.

ImprovemenFeature

Most helpful comment

btw - data leakage was partially addressed by #451. Right now you should see deprecation warnings about undefined/changing behavior. In 1.0, the new behavior is that an absent Meta.fields or Meta.fields = None does nothing.

All 11 comments

@nip3o Yep. Happy to see this.

I'd also like fields to require declared filters, as per DRF, while you're at it... 😉

btw - data leakage was partially addressed by #451. Right now you should see deprecation warnings about undefined/changing behavior. In 1.0, the new behavior is that an absent Meta.fields or Meta.fields = None does nothing.

I'd also like fields to require declared filters, as per DRF, while you're at it

I'm not sure how this would work with the dict syntax for Meta.fields. eg:

class UserFilter(filters.FilterSet):
    username = filters.CharFilter()

    class Meta:
        model = User
        fields = {
            'name': ['contains', 'startswith', 'endswith', ...'],
            ???
        }

I'm not sure how this would work with the dict syntax

Indeed.

I'm still kind of :-1: on this. I don't see a way of resolving the Meta.fields dict syntax w/ declared filters. At least, not in a way that isn't awkward:

class UserFilter(filters.FilterSet):
    username = filters.CharFilter(name='username', lookup_expr='exact')
    username_like = filters.CharFilter(name='username', lookup_expr='icontains')
    relevance = filters.CharFilter(method='filter_relevance')

    class Meta:
        model = User
        fields = {
            'name': ['contains', 'startswith', 'endswith', ...'],
            'username': ['exact'],  # match w/ the lookup_expr? 
            'username_like': ['icontains'],
            'relevance': [???]  # What about method filters?
        }

Additionally, this requirement runs into issues for custom model fields that don't inherit a field class found in the FilterSet.FILTER_DEFAULTS. Custom fields would require the following:

class NetworkSettingFilter(FilterSet):
    mask = MaskFilter(...)

    class Meta:
        model = NetworkSetting
        fields = ['mask']
        filter_overrides = {
            SubnetMaskField: {'filter_class': MaskFilter},
        }

# vs.

class NetworkSettingFilter(FilterSet):
    mask = MaskFilter(...)

    class Meta:
        model = NetworkSetting

Side note: this later point is something we _could_ fix. We'd have to rewrite auto-generation to behave like DRF's serializers instead of like Django's model forms. Either way, my main concern is the syntax conflict.

OK. Let's leave it for now.

I like the original proposal. I'm inclined to scope it to _If fields is a list..._ But I need to think about it a little more.

A bit of back and forth but I'm going to re-milestone this just for the original issue.

Either fields or exclude should be declared. fields = '__all__' should be the minimum.

(Not enforcing that the lists match is something we can come back to.)

The relevant DRF code is here:

        assert not (fields is None and exclude is None), (
            "Creating a ModelSerializer without either the 'fields' attribute "
            "or the 'exclude' attribute has been deprecated since 3.3.0, "
            "and is now disallowed. Add an explicit fields = '__all__' to the "
            "{serializer_class} serializer.".format(
                serializer_class=self.__class__.__name__
            ),
        )

We need a similar addition in filters_for_model:

    # Setting exclude with no fields implies all other fields.
    if exclude is not None and fields is None:
        fields = ALL_FIELDS

It'll need to be deprecated to begin.

Either fields or exclude should be declared. fields = '__all__' should be the minimum.

I can definitely get behind that. Currently working on a PR.

Cool.

Thinking about, don't worry about the depreciation. It's a small change, on a major version change. Just make sure to call it out in the change notes. 👍🏽

Thinking about, don't worry about the depreciation. It's a small change, on a major version change. Just make sure to call it out in the change notes.

The intent is slightly different, but we actually kind of covered this here.

Also, currently refactoring filters_for_model to not attempt filter generation for declared filters. (This is relevant to the subnet/mask example where fields = ['mask'] would raise an unnecessary exception.

Closed by #550

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lalzada picture lalzada  ·  3Comments

GuillaumeCisco picture GuillaumeCisco  ·  3Comments

sassanh picture sassanh  ·  4Comments

blueyed picture blueyed  ·  4Comments

jwineinger picture jwineinger  ·  3Comments