Permalink
Browse files

Update to django 1.4.4 r=peterbe

  • Loading branch information...
1 parent baf3a33 commit 72cace3d90da618dda7efaa89261961f08137091 @willkg willkg committed Feb 19, 2013
@@ -1,4 +1,4 @@
-VERSION = (1, 4, 3, 'final', 0)
+VERSION = (1, 4, 4, 'final', 0)
def get_version(version=None):
"""Derives a PEP386-compliant version number from VERSION."""
@@ -29,6 +29,10 @@
# * Receive x-headers
INTERNAL_IPS = ()
+# Hosts/domain names that are valid for this site.
+# "*" matches anything, ".example.com" matches example.com and all subdomains
+ALLOWED_HOSTS = ['*']
+
# Local time zone for this installation. All choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
# systems may support all possibilities). When USE_TZ is True, this is
@@ -20,6 +20,10 @@
}
}
+# Hosts/domain names that are valid for this site; required if DEBUG is False
+# See https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#allowed-hosts
+ALLOWED_HOSTS = []
+
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
@@ -1317,15 +1317,21 @@ def delete_view(self, request, object_id, extra_context=None):
def history_view(self, request, object_id, extra_context=None):
"The 'history' admin view for this model."
from django.contrib.admin.models import LogEntry
+ # First check if the user can see this history.
model = self.model
+ obj = get_object_or_404(model, pk=unquote(object_id))
+
+ if not self.has_change_permission(request, obj):
+ raise PermissionDenied
+
+ # Then get the history for this object.
opts = model._meta
app_label = opts.app_label
action_list = LogEntry.objects.filter(
object_id = object_id,
content_type__id__exact = ContentType.objects.get_for_model(model).id
).select_related().order_by('action_time')
- # If no history was found, see whether this object even exists.
- obj = get_object_or_404(model, pk=unquote(object_id))
+
context = {
'title': _('Change history: %s') % force_unicode(obj),
'action_list': action_list,
@@ -35,7 +35,8 @@ def check_password(password, encoded, setter=None, preferred='default'):
password = smart_str(password)
encoded = smart_str(encoded)
- if len(encoded) == 32 and '$' not in encoded:
+ if ((len(encoded) == 32 and '$' not in encoded) or
+ (len(encoded) == 37 and encoded.startswith('md5$$'))):
hasher = get_hasher('unsalted_md5')
else:
algorithm = encoded.split('$', 1)[0]
@@ -347,6 +348,8 @@ def encode(self, password, salt):
return hashlib.md5(password).hexdigest()
def verify(self, password, encoded):
+ if len(encoded) == 37 and encoded.startswith('md5$$'):
+ encoded = encoded[5:]
encoded_2 = self.encode(password, '')
return constant_time_compare(encoded, encoded_2)
@@ -59,6 +59,11 @@ def test_unsalted_md5(self):
self.assertTrue(is_password_usable(encoded))
self.assertTrue(check_password(u'letmein', encoded))
self.assertFalse(check_password('letmeinz', encoded))
+ # Alternate unsalted syntax
+ alt_encoded = "md5$$%s" % encoded
+ self.assertTrue(is_password_usable(alt_encoded))
+ self.assertTrue(check_password(u'letmein', alt_encoded))
+ self.assertFalse(check_password('letmeinz', alt_encoded))
@skipUnless(crypt, "no crypt module to generate password.")
def test_crypt(self):
@@ -107,6 +107,7 @@ def test_email_found_custom_from(self):
self.assertEqual(len(mail.outbox), 1)
self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)
+ @override_settings(ALLOWED_HOSTS=['adminsite.com'])
def test_admin_reset(self):
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
response = self.client.post('/admin_password_reset/',
@@ -9,6 +9,7 @@
from django.http import HttpRequest, Http404
from django.test import TestCase
from django.utils.encoding import smart_str
+from django.test.utils import override_settings
class FooWithoutUrl(models.Model):
@@ -114,6 +115,7 @@ def test_get_for_models_full_cache(self):
FooWithUrl: ContentType.objects.get_for_model(FooWithUrl),
})
+ @override_settings(ALLOWED_HOSTS=['example.com'])
def test_shortcut_view(self):
"""
Check that the shortcut view (used for the admin "view on site"
@@ -68,23 +68,27 @@ def test_time_field(self):
layer_key=AllOGRFields._meta.db_table,
decimal=['f_decimal'])
- expected = [
- '# This is an auto-generated Django model module created by ogrinspect.',
- 'from django.contrib.gis.db import models',
- '',
- 'class Measurement(models.Model):',
- ' f_decimal = models.DecimalField(max_digits=0, decimal_places=0)',
- ' f_int = models.IntegerField()',
- ' f_datetime = models.DateTimeField()',
- ' f_time = models.TimeField()',
- ' f_float = models.FloatField()',
- ' f_char = models.CharField(max_length=10)',
- ' f_date = models.DateField()',
- ' geom = models.PolygonField()',
- ' objects = models.GeoManager()',
- ]
+ self.assertTrue(model_def.startswith(
+ '# This is an auto-generated Django model module created by ogrinspect.\n'
+ 'from django.contrib.gis.db import models\n'
+ '\n'
+ 'class Measurement(models.Model):\n'
+ ))
+
+ # The ordering of model fields might vary depending on several factors (version of GDAL, etc.)
+ self.assertIn(' f_decimal = models.DecimalField(max_digits=0, decimal_places=0)', model_def)
+ self.assertIn(' f_int = models.IntegerField()', model_def)
+ self.assertIn(' f_datetime = models.DateTimeField()', model_def)
+ self.assertIn(' f_time = models.TimeField()', model_def)
+ self.assertIn(' f_float = models.FloatField()', model_def)
+ self.assertIn(' f_char = models.CharField(max_length=10)', model_def)
+ self.assertIn(' f_date = models.DateField()', model_def)
+
+ self.assertTrue(model_def.endswith(
+ ' geom = models.PolygonField()\n'
+ ' objects = models.GeoManager()'
+ ))
- self.assertEqual(model_def, '\n'.join(expected))
def get_ogr_db_string():
# Construct the DB string that GDAL will use to inspect the database.
@@ -3,6 +3,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpRequest
from django.test import TestCase
+from django.test.utils import override_settings
class SitesFrameworkTests(TestCase):
@@ -39,6 +40,7 @@ def test_site_cache(self):
site = Site.objects.get_current()
self.assertEqual(u"Example site", site.name)
+ @override_settings(ALLOWED_HOSTS=['example.com'])
def test_get_current_site(self):
# Test that the correct Site object is returned
request = HttpRequest()
@@ -8,6 +8,8 @@
from django.utils.xmlutils import SimplerXMLGenerator
from django.utils.encoding import smart_unicode
from xml.dom import pulldom
+from xml.sax import handler
+from xml.sax.expatreader import ExpatParser as _ExpatParser
class Serializer(base.Serializer):
"""
@@ -149,9 +151,13 @@ class Deserializer(base.Deserializer):
def __init__(self, stream_or_string, **options):
super(Deserializer, self).__init__(stream_or_string, **options)
- self.event_stream = pulldom.parse(self.stream)
+ self.event_stream = pulldom.parse(self.stream, self._make_parser())
self.db = options.pop('using', DEFAULT_DB_ALIAS)
+ def _make_parser(self):
+ """Create a hardened XML parser (no custom/external entities)."""
+ return DefusedExpatParser()
+
def next(self):
for event, node in self.event_stream:
if event == "START_ELEMENT" and node.nodeName == "object":
@@ -290,3 +296,90 @@ def getInnerText(node):
else:
pass
return u"".join(inner_text)
+
+
+# Below code based on Christian Heimes' defusedxml
+
+
+class DefusedExpatParser(_ExpatParser):
+ """
+ An expat parser hardened against XML bomb attacks.
+
+ Forbids DTDs, external entity references
+
+ """
+ def __init__(self, *args, **kwargs):
+ _ExpatParser.__init__(self, *args, **kwargs)
+ self.setFeature(handler.feature_external_ges, False)
+ self.setFeature(handler.feature_external_pes, False)
+
+ def start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
+ raise DTDForbidden(name, sysid, pubid)
+
+ def entity_decl(self, name, is_parameter_entity, value, base,
+ sysid, pubid, notation_name):
+ raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name)
+
+ def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
+ # expat 1.2
+ raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name)
+
+ def external_entity_ref_handler(self, context, base, sysid, pubid):
+ raise ExternalReferenceForbidden(context, base, sysid, pubid)
+
+ def reset(self):
+ _ExpatParser.reset(self)
+ parser = self._parser
+ parser.StartDoctypeDeclHandler = self.start_doctype_decl
+ parser.EntityDeclHandler = self.entity_decl
+ parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl
+ parser.ExternalEntityRefHandler = self.external_entity_ref_handler
+
+
+class DefusedXmlException(ValueError):
+ """Base exception."""
+ def __repr__(self):
+ return str(self)
+
+
+class DTDForbidden(DefusedXmlException):
+ """Document type definition is forbidden."""
+ def __init__(self, name, sysid, pubid):
+ super(DTDForbidden, self).__init__()
+ self.name = name
+ self.sysid = sysid
+ self.pubid = pubid
+
+ def __str__(self):
+ tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})"
+ return tpl.format(self.name, self.sysid, self.pubid)
+
+
+class EntitiesForbidden(DefusedXmlException):
+ """Entity definition is forbidden."""
+ def __init__(self, name, value, base, sysid, pubid, notation_name):
+ super(EntitiesForbidden, self).__init__()
+ self.name = name
+ self.value = value
+ self.base = base
+ self.sysid = sysid
+ self.pubid = pubid
+ self.notation_name = notation_name
+
+ def __str__(self):
+ tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})"
+ return tpl.format(self.name, self.sysid, self.pubid)
+
+
+class ExternalReferenceForbidden(DefusedXmlException):
+ """Resolving an external reference is forbidden."""
+ def __init__(self, context, base, sysid, pubid):
+ super(ExternalReferenceForbidden, self).__init__()
+ self.context = context
+ self.base = base
+ self.sysid = sysid
+ self.pubid = pubid
+
+ def __str__(self):
+ tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})"
+ return tpl.format(self.sysid, self.pubid)
@@ -42,8 +42,14 @@ def __setattr__(self, name, value):
# Register an event that closes the database connection
# when a Django request is finished.
def close_connection(**kwargs):
- for conn in connections.all():
- conn.close()
+ # Avoid circular imports
+ from django.db import transaction
+ for conn in connections:
+ # If an error happens here the connection will be left in broken
+ # state. Once a good db connection is again available, the
+ # connection state will be cleaned up.
+ transaction.abort(conn)
+ connections[conn].close()
signals.request_finished.connect(close_connection)
# Register an event that resets connection.queries
@@ -83,6 +83,17 @@ def _savepoint_commit(self, sid):
return
self.cursor().execute(self.ops.savepoint_commit_sql(sid))
+ def abort(self):
+ """
+ Roll back any ongoing transaction and clean the transaction state
+ stack.
+ """
+ if self._dirty:
+ self._rollback()
+ self._dirty = False
+ while self.transaction_state:
+ self.leave_transaction_management()
+
def enter_transaction_management(self, managed=True):
"""
Enters transaction management for a running thread. It must be balanced with
@@ -177,7 +177,7 @@ def _mysql_storage_engine(self):
# will tell you the default table type of the created
# table. Since all Django's test tables will have the same
# table type, that's enough to evaluate the feature.
- cursor.execute("SHOW TABLE STATUS WHERE Name='INTROSPECT_TEST'")
+ cursor.execute("SHOW TABLE STATUS LIKE 'INTROSPECT_TEST'")
result = cursor.fetchone()
cursor.execute('DROP TABLE INTROSPECT_TEST')
self._storage_engine = result[1]
@@ -544,12 +544,14 @@ def __init__(self, model=None, query_field_name=None, instance=None, symmetrical
"a many-to-many relationship can be used." %
instance.__class__.__name__)
-
def _get_fk_val(self, obj, field_name):
"""
Returns the correct value for this relationship's foreign key. This
might be something else than pk value when to_field is used.
"""
+ if not self.through:
+ # Make custom m2m fields with no through model defined usable.
+ return obj.pk
fk = self.through._meta.get_field(field_name)
if fk.rel.field_name and fk.rel.field_name != fk.rel.to._meta.pk.attname:
attname = fk.rel.get_related_field().get_attname()
@@ -25,6 +25,21 @@ class TransactionManagementError(Exception):
"""
pass
+def abort(using=None):
+ """
+ Roll back any ongoing transactions and clean the transaction management
+ state of the connection.
+
+ This method is to be used only in cases where using balanced
+ leave_transaction_management() calls isn't possible. For example after a
+ request has finished, the transaction state isn't known, yet the connection
+ must be cleaned up for the next request.
+ """
+ if using is None:
+ using = DEFAULT_DB_ALIAS
+ connection = connections[using]
+ connection.abort()
+
def enter_transaction_management(managed=True, using=None):
"""
Enters transaction management for a running thread. It must be balanced with
@@ -19,6 +19,9 @@
ORDERING_FIELD_NAME = 'ORDER'
DELETION_FIELD_NAME = 'DELETE'
+# default maximum number of forms in a formset, to prevent memory exhaustion
+DEFAULT_MAX_NUM = 1000
+
class ManagementForm(Form):
"""
``ManagementForm`` is used to keep track of how many form instances
@@ -111,7 +114,7 @@ def initial_form_count(self):
def _construct_forms(self):
# instantiate all the forms and put them in self.forms
self.forms = []
- for i in xrange(self.total_form_count()):
+ for i in xrange(min(self.total_form_count(), self.absolute_max)):
self.forms.append(self._construct_form(i))
def _construct_form(self, i, **kwargs):
@@ -360,9 +363,14 @@ def as_ul(self):
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
can_delete=False, max_num=None):
"""Return a FormSet for the given form class."""
+ if max_num is None:
+ max_num = DEFAULT_MAX_NUM
+ # hard limit on forms instantiated, to prevent memory-exhaustion attacks
+ # limit defaults to DEFAULT_MAX_NUM, but developer can increase it via max_num
+ absolute_max = max(DEFAULT_MAX_NUM, max_num)
attrs = {'form': form, 'extra': extra,
'can_order': can_order, 'can_delete': can_delete,
- 'max_num': max_num}
+ 'max_num': max_num, 'absolute_max': absolute_max}
return type(form.__name__ + 'FormSet', (formset,), attrs)
def all_valid(formsets):
Oops, something went wrong.

0 comments on commit 72cace3

Please sign in to comment.