Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[1.1.X] Fix a security issue in the admin. Disclosure and new release…

… forthcoming.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@15035 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 17084839fd7e267da5729f2a27753322b9d415a0 1 parent 934dc9e
Alex Gaynor authored December 23, 2010
28  django/contrib/admin/options.py
@@ -8,7 +8,9 @@
8 8
 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
9 9
 from django.core.exceptions import PermissionDenied, ValidationError
10 10
 from django.db import models, transaction
11  
-from django.db.models.fields import BLANK_CHOICE_DASH
  11
+from django.db.models.related import RelatedObject
  12
+from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
  13
+from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
12 14
 from django.http import Http404, HttpResponse, HttpResponseRedirect
13 15
 from django.shortcuts import get_object_or_404, render_to_response
14 16
 from django.utils.datastructures import SortedDict
@@ -171,6 +173,30 @@ def _declared_fieldsets(self):
171 173
         return None
172 174
     declared_fieldsets = property(_declared_fieldsets)
173 175
 
  176
+    def lookup_allowed(self, lookup):
  177
+        parts = lookup.split(LOOKUP_SEP)
  178
+
  179
+        # Last term in lookup is a query term (__exact, __startswith etc)
  180
+        # This term can be ignored.
  181
+        if len(parts) > 1 and parts[-1] in QUERY_TERMS:
  182
+            parts.pop()
  183
+
  184
+        # Special case -- foo__id__exact and foo__id queries are implied
  185
+        # if foo has been specificially included in the lookup list; so
  186
+        # drop __id if it is the last part.
  187
+        if len(parts) > 1 and parts[-1] == self.model._meta.pk.name:
  188
+            parts.pop()
  189
+
  190
+        try:
  191
+            self.model._meta.get_field_by_name(parts[0])
  192
+        except FieldDoesNotExist:
  193
+            # Lookups on non-existants fields are ok, since they're ignored
  194
+            # later.
  195
+            return True
  196
+        else:
  197
+            clean_lookup = LOOKUP_SEP.join(parts)
  198
+            return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
  199
+
174 200
 class ModelAdmin(BaseModelAdmin):
175 201
     "Encapsulates all admin options and functionality for a given model."
176 202
 
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
  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.db.models.query import QuerySet
@@ -192,13 +193,18 @@ def get_query_set(self):
192 193
                 else:
193 194
                     lookup_params[key] = True
194 195
 
  196
+            if not self.model_admin.lookup_allowed(key):
  197
+                raise SuspiciousOperation(
  198
+                    "Filtering by %s not allowed" % key
  199
+                )
  200
+
195 201
         # Apply lookup parameters from the query string.
196 202
         try:
197 203
             qs = qs.filter(**lookup_params)
198 204
         # Naked except! Because we don't have any other way of validating "params".
199 205
         # They might be invalid if the keyword arguments are incorrect, or if the
200 206
         # values are not in the correct type, so we might get FieldError, ValueError,
201  
-        # ValicationError, or ? from a custom field that raises yet something else 
  207
+        # ValicationError, or ? from a custom field that raises yet something else
202 208
         # when handed impossible data.
203 209
         except:
204 210
             raise IncorrectLookupParameters
12  tests/regressiontests/admin_views/models.py
@@ -7,7 +7,7 @@
7 7
 from django.core.mail import EmailMessage
8 8
 from django import forms
9 9
 from django.forms.models import BaseModelFormSet
10  
-
  10
+from django.contrib.auth.models import User
11 11
 from django.contrib.contenttypes import generic
12 12
 from django.contrib.contenttypes.models import ContentType
13 13
 
@@ -89,7 +89,7 @@ class ChapterInline(admin.TabularInline):
89 89
 
90 90
 class ArticleAdmin(admin.ModelAdmin):
91 91
     list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year')
92  
-    list_filter = ('date',)
  92
+    list_filter = ('date', 'section')
93 93
 
94 94
     def changelist_view(self, request):
95 95
         "Test that extra_context works"
@@ -505,6 +505,13 @@ class CyclicTwo(models.Model):
505 505
     def __unicode__(self):
506 506
         return self.name
507 507
 
  508
+class Album(models.Model):
  509
+    owner = models.ForeignKey(User)
  510
+    title = models.CharField(max_length=30)
  511
+
  512
+class AlbumAdmin(admin.ModelAdmin):
  513
+    list_filter = ['title']
  514
+
508 515
 admin.site.register(Article, ArticleAdmin)
509 516
 admin.site.register(CustomArticle, CustomArticleAdmin)
510 517
 admin.site.register(Section, save_as=True, inlines=[ArticleInline])
@@ -547,3 +554,4 @@ def __unicode__(self):
547 554
 admin.site.register(Book, inlines=[ChapterInline])
548 555
 admin.site.register(Promo)
549 556
 admin.site.register(ChapterXtra1)
  557
+admin.site.register(Album, AlbumAdmin)
5  tests/regressiontests/admin_views/tests.py
@@ -3,6 +3,7 @@
3 3
 import re
4 4
 import datetime
5 5
 from django.conf import settings
  6
+from django.core.exceptions import SuspiciousOperation
6 7
 from django.core.files import temp as tempfile
7 8
 from django.contrib.auth.models import User, Permission
8 9
 from django.contrib.contenttypes.models import ContentType
@@ -289,6 +290,10 @@ def testI18NLanguageNonEnglishFallback(self):
289 290
         self.assertContains(response, 'Choisir une heure')
290 291
         deactivate()
291 292
 
  293
+    def test_disallowed_filtering(self):
  294
+        self.assertRaises(SuspiciousOperation,
  295
+            self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
  296
+        )
292 297
 
293 298
 class SaveAsTests(TestCase):
294 299
     fixtures = ['admin-views-users.xml','admin-views-person.xml']

0 notes on commit 1708483

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