Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fix a security issue in the admin. Disclosure and new release forthco…

…ming.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15031 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 732198ed5c2a127249970e0cd4218d093a38e9a2 1 parent 5ed6e7a
Alex Gaynor authored
41  django/contrib/admin/options.py
@@ -11,7 +11,9 @@
11 11
 from django.core.exceptions import PermissionDenied, ValidationError
12 12
 from django.core.paginator import Paginator
13 13
 from django.db import models, transaction, router
14  
-from django.db.models.fields import BLANK_CHOICE_DASH
  14
+from django.db.models.related import RelatedObject
  15
+from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
  16
+from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
15 17
 from django.http import Http404, HttpResponse, HttpResponseRedirect
16 18
 from django.shortcuts import get_object_or_404, render_to_response
17 19
 from django.utils.decorators import method_decorator
@@ -203,6 +205,43 @@ def queryset(self, request):
203 205
             qs = qs.order_by(*ordering)
204 206
         return qs
205 207
 
  208
+    def lookup_allowed(self, lookup):
  209
+        parts = lookup.split(LOOKUP_SEP)
  210
+
  211
+        # Last term in lookup is a query term (__exact, __startswith etc)
  212
+        # This term can be ignored.
  213
+        if len(parts) > 1 and parts[-1] in QUERY_TERMS:
  214
+            parts.pop()
  215
+
  216
+        # Special case -- foo__id__exact and foo__id queries are implied
  217
+        # if foo has been specificially included in the lookup list; so
  218
+        # drop __id if it is the last part. However, first we need to find
  219
+        # the pk attribute name.
  220
+        model = self.model
  221
+        pk_attr_name = None
  222
+        for part in parts[:-1]:
  223
+            field, _, _, _ = model._meta.get_field_by_name(part)
  224
+            if hasattr(field, 'rel'):
  225
+                model = field.rel.to
  226
+                pk_attr_name = model._meta.pk.name
  227
+            elif isinstance(field, RelatedObject):
  228
+                model = field.model
  229
+                pk_attr_name = model._meta.pk.name
  230
+            else:
  231
+                pk_attr_name = None
  232
+        if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name:
  233
+            parts.pop()
  234
+
  235
+        try:
  236
+            self.model._meta.get_field_by_name(parts[0])
  237
+        except FieldDoesNotExist:
  238
+            # Lookups on non-existants fields are ok, since they're ignored
  239
+            # later.
  240
+            return True
  241
+        else:
  242
+            clean_lookup = LOOKUP_SEP.join(parts)
  243
+            return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
  244
+
206 245
 class ModelAdmin(BaseModelAdmin):
207 246
     "Encapsulates all admin options and functionality for a given model."
208 247
 
8  django/contrib/admin/views/main.py
... ...
@@ -1,6 +1,7 @@
1 1
 from django.contrib.admin.filterspecs import FilterSpec
2 2
 from django.contrib.admin.options import IncorrectLookupParameters
3 3
 from django.contrib.admin.util import quote, get_fields_from_path
  4
+from django.core.exceptions import SuspiciousOperation
4 5
 from django.core.paginator import Paginator, InvalidPage
5 6
 from django.db import models
6 7
 from django.utils.encoding import force_unicode, smart_str
@@ -188,13 +189,18 @@ def get_query_set(self):
188 189
                 else:
189 190
                     lookup_params[key] = True
190 191
 
  192
+            if not self.model_admin.lookup_allowed(key):
  193
+                raise SuspiciousOperation(
  194
+                    "Filtering by %s not allowed" % key
  195
+                )
  196
+
191 197
         # Apply lookup parameters from the query string.
192 198
         try:
193 199
             qs = qs.filter(**lookup_params)
194 200
         # Naked except! Because we don't have any other way of validating "params".
195 201
         # They might be invalid if the keyword arguments are incorrect, or if the
196 202
         # values are not in the correct type, so we might get FieldError, ValueError,
197  
-        # ValicationError, or ? from a custom field that raises yet something else 
  203
+        # ValicationError, or ? from a custom field that raises yet something else
198 204
         # when handed impossible data.
199 205
         except:
200 206
             raise IncorrectLookupParameters
7  tests/regressiontests/admin_views/models.py
@@ -100,7 +100,7 @@ class ChapterXtra1Admin(admin.ModelAdmin):
100 100
 
101 101
 class ArticleAdmin(admin.ModelAdmin):
102 102
     list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year')
103  
-    list_filter = ('date',)
  103
+    list_filter = ('date', 'section')
104 104
 
105 105
     def changelist_view(self, request):
106 106
         "Test that extra_context works"
@@ -611,6 +611,9 @@ class Album(models.Model):
611 611
     owner = models.ForeignKey(User)
612 612
     title = models.CharField(max_length=30)
613 613
 
  614
+class AlbumAdmin(admin.ModelAdmin):
  615
+    list_filter = ['title']
  616
+
614 617
 admin.site.register(Article, ArticleAdmin)
615 618
 admin.site.register(CustomArticle, CustomArticleAdmin)
616 619
 admin.site.register(Section, save_as=True, inlines=[ArticleInline])
@@ -657,4 +660,4 @@ class Album(models.Model):
657 660
 admin.site.register(ChapterXtra1, ChapterXtra1Admin)
658 661
 admin.site.register(Pizza, PizzaAdmin)
659 662
 admin.site.register(Topping)
660  
-admin.site.register(Album)
  663
+admin.site.register(Album, AlbumAdmin)
10  tests/regressiontests/admin_views/tests.py
@@ -5,6 +5,7 @@
5 5
 
6 6
 from django.conf import settings
7 7
 from django.core import mail
  8
+from django.core.exceptions import SuspiciousOperation
8 9
 from django.core.files import temp as tempfile
9 10
 from django.core.urlresolvers import reverse
10 11
 # Register auth models with the admin.
@@ -348,6 +349,15 @@ def testI18NLanguageNonEnglishFallback(self):
348 349
         self.assertContains(response, 'Choisir une heure')
349 350
         deactivate()
350 351
 
  352
+    def test_disallowed_filtering(self):
  353
+        self.assertRaises(SuspiciousOperation,
  354
+            self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
  355
+        )
  356
+
  357
+        try:
  358
+            self.client.get("/test_admin/admin/admin_views/stuff/?color__value__startswith=red")
  359
+        except SuspiciousOperation:
  360
+            self.fail("Filters are allowed if explicitly included in list_filter")
351 361
 
352 362
 class SaveAsTests(TestCase):
353 363
     fixtures = ['admin-views-users.xml','admin-views-person.xml']

0 notes on commit 732198e

Please sign in to comment.
Something went wrong with that request. Please try again.