diff -Nru python-django-1.11.9/debian/changelog python-django-1.11.11/debian/changelog --- python-django-1.11.9/debian/changelog 2018-01-04 23:08:15.000000000 +0000 +++ python-django-1.11.11/debian/changelog 2018-03-21 13:42:10.000000000 +0000 @@ -1,3 +1,50 @@ +python-django (1:1.11.11-1ubuntu1) bionic; urgency=medium + + * Merge from Debian unstable. Remaining changes: + - debian/patches/pymysql-replacement.patch: Use pymysql as drop in + replacement for MySQLdb. + - debian/control: Drop python-mysqldb in favor of python-pymysql. + + -- Marc Deslauriers Wed, 21 Mar 2018 09:42:10 -0400 + +python-django (1:1.11.11-1) unstable; urgency=medium + + [ Chris Lamb ] + * New upstream security release: + - CVE-2018-7536: Denial-of-service possibility in urlize and urlizetrunc + template filters. + - CVE-2018-7537: Denial-of-service possibility in truncatechars_html and + truncatewords_html template filters + + [ Ondřej Nový ] + * d/control: Set Vcs-* to salsa.debian.org + + -- Chris Lamb Tue, 06 Mar 2018 22:44:12 -0800 + +python-django (1:1.11.10-1ubuntu1) bionic; urgency=low + + * Merge from Debian unstable. Remaining changes: + - debian/patches/pymysql-replacement.patch: Use pymysql as drop in + replacement for MySQLdb. + - debian/control: Drop python-mysqldb in favor of python-pymysql. + + -- Steve Langasek Fri, 23 Feb 2018 11:56:40 -0800 + +python-django (1:1.11.10-1) unstable; urgency=medium + + * New upstream security release: + - CVE-2018-6188: A regression in Django 1.11.8 made + django.contrib.auth.forms.AuthenticationForm run its + confirm_login_allowed() method even if an incorrect password is entered. + This can leak information about a user, depending on what messages + confirm_login_allowed() raises. If confirm_login_allowed() isn't + overridden, an attacker enter an arbitrary username and see if that user + has been set to is_active=False. If confirm_login_allowed() is + overridden, more sensitive details could be leaked. + * Use HTTPS "Format" URI in debian/copyright. + + -- Chris Lamb Thu, 01 Feb 2018 17:42:06 +0000 + python-django (1:1.11.9-1ubuntu1) bionic; urgency=low * Merge from Debian unstable. Remaining changes: diff -Nru python-django-1.11.9/debian/control python-django-1.11.11/debian/control --- python-django-1.11.9/debian/control 2018-01-02 16:15:12.000000000 +0000 +++ python-django-1.11.11/debian/control 2018-03-21 13:42:10.000000000 +0000 @@ -47,8 +47,8 @@ Build-Depends-Indep: libjs-jquery, Homepage: http://www.djangoproject.com/ -Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/python-django.git -Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/python-django.git +Vcs-Git: https://salsa.debian.org/python-team/modules/python-django.git +Vcs-Browser: https://salsa.debian.org/python-team/modules/python-django X-Python-Version: >= 2.7 Package: python-django diff -Nru python-django-1.11.9/debian/copyright python-django-1.11.11/debian/copyright --- python-django-1.11.9/debian/copyright 2018-01-02 16:15:12.000000000 +0000 +++ python-django-1.11.11/debian/copyright 2018-03-07 06:44:12.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Django Upstream-Contact: https://groups.google.com/group/django-developers Source: https://www.djangoproject.com/download/ diff -Nru python-django-1.11.9/django/contrib/admin/widgets.py python-django-1.11.11/django/contrib/admin/widgets.py --- python-django-1.11.9/django/contrib/admin/widgets.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/django/contrib/admin/widgets.py 2018-03-06 14:07:36.000000000 +0000 @@ -6,6 +6,7 @@ import copy from django import forms +from django.core.exceptions import ValidationError from django.db.models.deletion import CASCADE from django.urls import reverse from django.urls.exceptions import NoReverseMatch @@ -174,7 +175,7 @@ key = self.rel.get_related_field().name try: obj = self.rel.model._default_manager.using(self.db).get(**{key: value}) - except (ValueError, self.rel.model.DoesNotExist): + except (ValueError, self.rel.model.DoesNotExist, ValidationError): return '', '' try: diff -Nru python-django-1.11.9/django/contrib/auth/forms.py python-django-1.11.11/django/contrib/auth/forms.py --- python-django-1.11.9/django/contrib/auth/forms.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/django/contrib/auth/forms.py 2018-03-06 14:07:36.000000000 +0000 @@ -186,15 +186,6 @@ if username is not None and password: self.user_cache = authenticate(self.request, username=username, password=password) if self.user_cache is None: - # An authentication backend may reject inactive users. Check - # if the user exists and is inactive, and raise the 'inactive' - # error if so. - try: - self.user_cache = UserModel._default_manager.get_by_natural_key(username) - except UserModel.DoesNotExist: - pass - else: - self.confirm_login_allowed(self.user_cache) raise forms.ValidationError( self.error_messages['invalid_login'], code='invalid_login', diff -Nru python-django-1.11.9/django/contrib/auth/__init__.py python-django-1.11.11/django/contrib/auth/__init__.py --- python-django-1.11.9/django/contrib/auth/__init__.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/django/contrib/auth/__init__.py 2018-03-06 14:07:36.000000000 +0000 @@ -82,6 +82,7 @@ def _authenticate_with_backend(backend, backend_path, request, credentials): + credentials = credentials.copy() # Prevent a mutation from propagating. args = (request,) # Does the backend accept a request argument? try: diff -Nru python-django-1.11.9/django/contrib/messages/__init__.py python-django-1.11.11/django/contrib/messages/__init__.py --- python-django-1.11.9/django/contrib/messages/__init__.py 2017-10-11 13:44:59.000000000 +0000 +++ python-django-1.11.11/django/contrib/messages/__init__.py 2018-03-06 13:57:56.000000000 +0000 @@ -1,5 +1,4 @@ from django.contrib.messages.api import * # NOQA from django.contrib.messages.constants import * # NOQA - default_app_config = 'django.contrib.messages.apps.MessagesConfig' diff -Nru python-django-1.11.9/django/core/management/commands/runserver.py python-django-1.11.11/django/core/management/commands/runserver.py --- python-django-1.11.9/django/core/management/commands/runserver.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/django/core/management/commands/runserver.py 2018-03-06 14:07:36.000000000 +0000 @@ -15,7 +15,6 @@ from django.utils import autoreload, six from django.utils.encoding import force_text, get_system_encoding - naiveip_re = re.compile(r"""^(?: (?P (?P\d{1,3}(?:\.\d{1,3}){3}) | # IPv4 address diff -Nru python-django-1.11.9/django/db/models/deletion.py python-django-1.11.11/django/db/models/deletion.py --- python-django-1.11.9/django/db/models/deletion.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/django/db/models/deletion.py 2018-03-06 14:07:36.000000000 +0000 @@ -286,8 +286,8 @@ # update fields for model, instances_for_fieldvalues in six.iteritems(self.field_updates): - query = sql.UpdateQuery(model) for (field, value), instances in six.iteritems(instances_for_fieldvalues): + query = sql.UpdateQuery(model) query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) diff -Nru python-django-1.11.9/django/db/models/signals.py python-django-1.11.11/django/db/models/signals.py --- python-django-1.11.9/django/db/models/signals.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/django/db/models/signals.py 2018-03-06 14:07:36.000000000 +0000 @@ -6,7 +6,6 @@ from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning - class_prepared = Signal(providing_args=["class"]) diff -Nru python-django-1.11.9/django/__init__.py python-django-1.11.11/django/__init__.py --- python-django-1.11.9/django/__init__.py 2018-01-02 00:52:56.000000000 +0000 +++ python-django-1.11.11/django/__init__.py 2018-03-06 14:07:47.000000000 +0000 @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 9, 'final', 0) +VERSION = (1, 11, 11, 'final', 0) __version__ = get_version(VERSION) diff -Nru python-django-1.11.9/django/utils/html.py python-django-1.11.11/django/utils/html.py --- python-django-1.11.9/django/utils/html.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/django/utils/html.py 2018-03-06 14:07:36.000000000 +0000 @@ -17,12 +17,7 @@ from .html_parser import HTMLParseError, HTMLParser # Configuration for urlize() function. -TRAILING_PUNCTUATION_RE = re.compile( - '^' # Beginning of word - '(.*?)' # The URL in word - '([.,:;!]+)' # Allowed non-wrapping, trailing punctuation - '$' # End of word -) +TRAILING_PUNCTUATION_CHARS = '.,:;!' WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'), ('"', '"'), ('\'', '\'')] # List of possible strings used for bullets in bulleted lists. @@ -32,7 +27,6 @@ word_split_re = re.compile(r'''([\s<>"']+)''') simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE) simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)($|/.*)$', re.IGNORECASE) -simple_email_re = re.compile(r'^\S+@\S+\.\S+$') @keep_lazy(six.text_type, SafeText) @@ -280,10 +274,10 @@ trimmed_something = False # Trim trailing punctuation. - match = TRAILING_PUNCTUATION_RE.match(middle) - if match: - middle = match.group(1) - trail = match.group(2) + trail + stripped = middle.rstrip(TRAILING_PUNCTUATION_CHARS) + if middle != stripped: + trail = middle[len(stripped):] + trail + middle = stripped trimmed_something = True # Trim wrapping punctuation. @@ -300,6 +294,21 @@ trimmed_something = True return lead, middle, trail + def is_email_simple(value): + """Return True if value looks like an email address.""" + # An @ must be in the middle of the value. + if '@' not in value or value.startswith('@') or value.endswith('@'): + return False + try: + p1, p2 = value.split('@') + except ValueError: + # value contains more than one @. + return False + # Dot must be in p2 (e.g. example.com) + if '.' not in p2 or p2.startswith('.'): + return False + return True + words = word_split_re.split(force_text(text)) for i, word in enumerate(words): if '.' in word or '@' in word or ':' in word: @@ -319,7 +328,7 @@ elif simple_url_2_re.match(middle): middle, middle_unescaped, trail = unescape(middle, trail) url = smart_urlquote('http://%s' % middle_unescaped) - elif ':' not in middle and simple_email_re.match(middle): + elif ':' not in middle and is_email_simple(middle): local, domain = middle.rsplit('@', 1) try: domain = domain.encode('idna').decode('ascii') diff -Nru python-django-1.11.9/django/utils/text.py python-django-1.11.11/django/utils/text.py --- python-django-1.11.9/django/utils/text.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/django/utils/text.py 2018-03-06 14:07:36.000000000 +0000 @@ -29,7 +29,7 @@ # Set up regular expressions re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.U | re.S) re_chars = re.compile(r'<.*?>|(.)', re.U | re.S) -re_tag = re.compile(r'<(/)?([^ ]+?)(?:(\s*/)| .*?)?>', re.S) +re_tag = re.compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S) re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines re_camel_case = re.compile(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))') diff -Nru python-django-1.11.9/Django.egg-info/PKG-INFO python-django-1.11.11/Django.egg-info/PKG-INFO --- python-django-1.11.9/Django.egg-info/PKG-INFO 2018-01-02 00:54:13.000000000 +0000 +++ python-django-1.11.11/Django.egg-info/PKG-INFO 2018-03-06 14:08:41.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: Django -Version: 1.11.9 +Version: 1.11.11 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. Home-page: https://www.djangoproject.com/ Author: Django Software Foundation diff -Nru python-django-1.11.9/Django.egg-info/SOURCES.txt python-django-1.11.11/Django.egg-info/SOURCES.txt --- python-django-1.11.9/Django.egg-info/SOURCES.txt 2018-01-02 00:54:14.000000000 +0000 +++ python-django-1.11.11/Django.egg-info/SOURCES.txt 2018-03-06 14:08:41.000000000 +0000 @@ -3538,6 +3538,8 @@ docs/releases/1.10.8.txt docs/releases/1.10.txt docs/releases/1.11.1.txt +docs/releases/1.11.10.txt +docs/releases/1.11.11.txt docs/releases/1.11.2.txt docs/releases/1.11.3.txt docs/releases/1.11.4.txt @@ -3633,6 +3635,7 @@ docs/releases/1.8.16.txt docs/releases/1.8.17.txt docs/releases/1.8.18.txt +docs/releases/1.8.19.txt docs/releases/1.8.2.txt docs/releases/1.8.3.txt docs/releases/1.8.4.txt diff -Nru python-django-1.11.9/docs/howto/custom-management-commands.txt python-django-1.11.11/docs/howto/custom-management-commands.txt --- python-django-1.11.9/docs/howto/custom-management-commands.txt 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/docs/howto/custom-management-commands.txt 2018-03-06 14:07:36.000000000 +0000 @@ -259,7 +259,8 @@ .. attribute:: BaseCommand.leave_locale_alone A boolean indicating whether the locale set in settings should be preserved - during the execution of the command instead of being forcibly set to 'en-us'. + during the execution of the command instead of translations being + deactivated. Default value is ``False``. @@ -267,9 +268,8 @@ this option in your custom command if it creates database content that is locale-sensitive and such content shouldn't contain any translations (like it happens e.g. with :mod:`django.contrib.auth` permissions) as - making the locale differ from the de facto default 'en-us' might cause - unintended effects. See the `Management commands and locales`_ section - above for further details. + activating any locale might cause unintended effects. See the `Management + commands and locales`_ section above for further details. .. attribute:: BaseCommand.style diff -Nru python-django-1.11.9/docs/ref/contrib/gis/geos.txt python-django-1.11.11/docs/ref/contrib/gis/geos.txt --- python-django-1.11.9/docs/ref/contrib/gis/geos.txt 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/docs/ref/contrib/gis/geos.txt 2018-03-06 14:07:37.000000000 +0000 @@ -932,7 +932,7 @@ .. function:: fromstr(string, srid=None) :param string: string that contains spatial data - :type string: string + :type string: str :param srid: spatial reference identifier :type srid: int :rtype: a :class:`GEOSGeometry` corresponding to the spatial data in the string diff -Nru python-django-1.11.9/docs/ref/contrib/gis/install/spatialite.txt python-django-1.11.11/docs/ref/contrib/gis/install/spatialite.txt --- python-django-1.11.9/docs/ref/contrib/gis/install/spatialite.txt 2017-10-04 13:29:42.000000000 +0000 +++ python-django-1.11.11/docs/ref/contrib/gis/install/spatialite.txt 2018-03-06 14:07:37.000000000 +0000 @@ -21,14 +21,14 @@ __ https://www.gaia-gis.it/fossil/libspatialite __ https://www.gaia-gis.it/gaia-sins/ -.. _spatialite_source: - .. admonition:: ``SPATIALITE_LIBRARY_PATH`` setting required for SpatiaLite 4.2+ If you're using SpatiaLite 4.2+, you must put this in your settings:: SPATIALITE_LIBRARY_PATH = 'mod_spatialite' +.. _spatialite_source: + Installing from source ====================== diff -Nru python-django-1.11.9/docs/ref/models/querysets.txt python-django-1.11.11/docs/ref/models/querysets.txt --- python-django-1.11.9/docs/ref/models/querysets.txt 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/docs/ref/models/querysets.txt 2018-03-06 14:07:37.000000000 +0000 @@ -541,10 +541,10 @@ add it to an earlier ``values()`` clause instead. For example:: >>> from django.db.models import Count - >>> Blog.objects.values('author', entries=Count('entry')) - - >>> Blog.objects.values('author').annotate(entries=Count('entry')) - + >>> Blog.objects.values('entry__authors', entries=Count('entry')) + + >>> Blog.objects.values('entry__authors').annotate(entries=Count('entry')) + A few subtleties that are worth mentioning: diff -Nru python-django-1.11.9/docs/ref/templates/api.txt python-django-1.11.11/docs/ref/templates/api.txt --- python-django-1.11.9/docs/ref/templates/api.txt 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/docs/ref/templates/api.txt 2018-03-06 14:07:37.000000000 +0000 @@ -932,8 +932,10 @@ ``Template`` in memory. The cached ``Template`` instance is returned for subsequent requests to load the same template. - This loader is automatically enabled if :setting:`DEBUG` is ``False`` and - :setting:`OPTIONS['loaders'] ` isn't specified. + This loader is automatically enabled if :setting:`OPTIONS['loaders'] + ` isn't specified and :setting:`OPTIONS['debug'] + ` is ``False`` (the latter option defaults to the value + of :setting:`DEBUG`). You can also enable template caching with some custom template loaders using settings like this:: diff -Nru python-django-1.11.9/docs/ref/urls.txt python-django-1.11.11/docs/ref/urls.txt --- python-django-1.11.9/docs/ref/urls.txt 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/docs/ref/urls.txt 2018-03-06 14:07:37.000000000 +0000 @@ -74,14 +74,14 @@ :arg module: URLconf module (or module name) :arg namespace: Instance namespace for the URL entries being included - :type namespace: string + :type namespace: str :arg app_name: Application namespace for the URL entries being included - :type app_name: string + :type app_name: str :arg pattern_list: Iterable of :func:`django.conf.urls.url` instances :arg app_namespace: Application namespace for the URL entries being included - :type app_namespace: string + :type app_namespace: str :arg instance_namespace: Instance namespace for the URL entries being included - :type instance_namespace: string + :type instance_namespace: str See :ref:`including-other-urlconfs` and :ref:`namespaces-and-include`. diff -Nru python-django-1.11.9/docs/releases/1.11.10.txt python-django-1.11.11/docs/releases/1.11.10.txt --- python-django-1.11.9/docs/releases/1.11.10.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.11.11/docs/releases/1.11.10.txt 2018-03-06 13:57:56.000000000 +0000 @@ -0,0 +1,39 @@ +============================ +Django 1.11.10 release notes +============================ + +*February 1, 2018* + +Django 1.11.10 fixes a security issue and several bugs in 1.11.9. + +CVE-2018-6188: Information leakage in ``AuthenticationForm`` +============================================================ + +A regression in Django 1.11.8 made +:class:`~django.contrib.auth.forms.AuthenticationForm` run its +``confirm_login_allowed()`` method even if an incorrect password is entered. +This can leak information about a user, depending on what messages +``confirm_login_allowed()`` raises. If ``confirm_login_allowed()`` isn't +overridden, an attacker enter an arbitrary username and see if that user has +been set to ``is_active=False``. If ``confirm_login_allowed()`` is overridden, +more sensitive details could be leaked. + +This issue is fixed with the caveat that ``AuthenticationForm`` can no longer +raise the "This account is inactive." error if the authentication backend +rejects inactive users (the default authentication backend, ``ModelBackend``, +has done that since Django 1.10). This issue will be revisited for Django 2.1 +as a fix to address the caveat will likely be too invasive for inclusion in +older versions. + +Bugfixes +======== + +* Fixed incorrect foreign key nullification if a model has two foreign keys to + the same model and a target model is deleted (:ticket:`29016`). + +* Fixed a regression where ``contrib.auth.authenticate()`` crashes if an + authentication backend doesn't accept ``request`` and a later one does + (:ticket:`29071`). + +* Fixed crash when entering an invalid uuid in ``ModelAdmin.raw_id_fields`` + (:ticket:`29094`). diff -Nru python-django-1.11.9/docs/releases/1.11.11.txt python-django-1.11.11/docs/releases/1.11.11.txt --- python-django-1.11.9/docs/releases/1.11.11.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.11.11/docs/releases/1.11.11.txt 2018-03-06 13:57:56.000000000 +0000 @@ -0,0 +1,30 @@ +============================ +Django 1.11.11 release notes +============================ + +*March 6, 2018* + +Django 1.11.11 fixes two security issues in 1.11.10. + +CVE-2018-7536: Denial-of-service possibility in ``urlize`` and ``urlizetrunc`` template filters +=============================================================================================== + +The ``django.utils.html.urlize()`` function was extremely slow to evaluate +certain inputs due to catastrophic backtracking vulnerabilities in two regular +expressions. The ``urlize()`` function is used to implement the ``urlize`` and +``urlizetrunc`` template filters, which were thus vulnerable. + +The problematic regular expressions are replaced with parsing logic that +behaves similarly. + +CVE-2018-7537: Denial-of-service possibility in ``truncatechars_html`` and ``truncatewords_html`` template filters +================================================================================================================== + +If ``django.utils.text.Truncator``'s ``chars()`` and ``words()`` methods were +passed the ``html=True`` argument, they were extremely slow to evaluate certain +inputs due to a catastrophic backtracking vulnerability in a regular +expression. The ``chars()`` and ``words()`` methods are used to implement the +``truncatechars_html`` and ``truncatewords_html`` template filters, which were +thus vulnerable. + +The backtracking problem in the regular expression is fixed. diff -Nru python-django-1.11.9/docs/releases/1.11.txt python-django-1.11.11/docs/releases/1.11.txt --- python-django-1.11.9/docs/releases/1.11.txt 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/docs/releases/1.11.txt 2018-03-06 14:07:37.000000000 +0000 @@ -695,8 +695,9 @@ 1.0) is removed. * The :class:`cached template loader ` - is now enabled if :setting:`DEBUG` is ``False`` and - :setting:`OPTIONS['loaders'] ` isn't specified. This could + is now enabled if :setting:`OPTIONS['loaders'] ` isn't + specified and :setting:`OPTIONS['debug'] ` is ``False`` + (the latter option defaults to the value of :setting:`DEBUG`). This could be backwards-incompatible if you have some :ref:`template tags that aren't thread safe `. diff -Nru python-django-1.11.9/docs/releases/1.8.19.txt python-django-1.11.11/docs/releases/1.8.19.txt --- python-django-1.11.9/docs/releases/1.8.19.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.11.11/docs/releases/1.8.19.txt 2018-03-06 13:54:10.000000000 +0000 @@ -0,0 +1,30 @@ +=========================== +Django 1.8.19 release notes +=========================== + +*March 6, 2018* + +Django 1.8.19 fixes two security issues in 1.18.18. + +CVE-2018-7536: Denial-of-service possibility in ``urlize`` and ``urlizetrunc`` template filters +=============================================================================================== + +The ``django.utils.html.urlize()`` function was extremely slow to evaluate +certain inputs due to a catastrophic backtracking vulnerability in a regular +expression. The ``urlize()`` function is used to implement the ``urlize`` and +``urlizetrunc`` template filters, which were thus vulnerable. + +The problematic regular expression is replaced with parsing logic that behaves +similarly. + +CVE-2018-7537: Denial-of-service possibility in ``truncatechars_html`` and ``truncatewords_html`` template filters +================================================================================================================== + +If ``django.utils.text.Truncator``'s ``chars()`` and ``words()`` methods were +passed the ``html=True`` argument, they were extremely slow to evaluate certain +inputs due to a catastrophic backtracking vulnerability in a regular +expression. The ``chars()`` and ``words()`` methods are used to implement the +``truncatechars_html`` and ``truncatewords_html`` template filters, which were +thus vulnerable. + +The backtracking problem in the regular expression is fixed. diff -Nru python-django-1.11.9/docs/releases/index.txt python-django-1.11.11/docs/releases/index.txt --- python-django-1.11.9/docs/releases/index.txt 2018-01-02 00:52:37.000000000 +0000 +++ python-django-1.11.11/docs/releases/index.txt 2018-03-06 14:07:37.000000000 +0000 @@ -26,6 +26,8 @@ .. toctree:: :maxdepth: 1 + 1.11.11 + 1.11.10 1.11.9 1.11.8 1.11.7 @@ -77,6 +79,7 @@ .. toctree:: :maxdepth: 1 + 1.8.19 1.8.18 1.8.17 1.8.16 diff -Nru python-django-1.11.9/docs/releases/security.txt python-django-1.11.11/docs/releases/security.txt --- python-django-1.11.9/docs/releases/security.txt 2017-10-04 13:29:42.000000000 +0000 +++ python-django-1.11.11/docs/releases/security.txt 2018-03-06 13:57:56.000000000 +0000 @@ -845,3 +845,15 @@ * Django 1.11 `(patch) `__ * Django 1.10 `(patch) `__ + +February 1, 2018 - :cve:`2018-6188` +----------------------------------- + +Information leakage in ``AuthenticationForm``. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.0 `(patch) `__ +* Django 1.11 `(patch) `__ diff -Nru python-django-1.11.9/docs/topics/i18n/translation.txt python-django-1.11.11/docs/topics/i18n/translation.txt --- python-django-1.11.9/docs/topics/i18n/translation.txt 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/docs/topics/i18n/translation.txt 2018-03-06 14:07:37.000000000 +0000 @@ -1822,13 +1822,13 @@ sign:: from django.utils.translation import ugettext as _ - output = _("10%% interest) + output = _("10%% interest") Or you can use ``no-python-format`` so that all percent signs are treated as literals:: # xgettext:no-python-format - output = _("10% interest) + output = _("10% interest") .. _creating-message-files-from-js-code: diff -Nru python-django-1.11.9/PKG-INFO python-django-1.11.11/PKG-INFO --- python-django-1.11.9/PKG-INFO 2018-01-02 00:54:15.000000000 +0000 +++ python-django-1.11.11/PKG-INFO 2018-03-06 14:08:44.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: Django -Version: 1.11.9 +Version: 1.11.11 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. Home-page: https://www.djangoproject.com/ Author: Django Software Foundation diff -Nru python-django-1.11.9/tests/admin_views/tests.py python-django-1.11.11/tests/admin_views/tests.py --- python-django-1.11.9/tests/admin_views/tests.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/admin_views/tests.py 2018-03-06 14:07:37.000000000 +0000 @@ -66,7 +66,6 @@ Worker, WorkHour, ) - ERROR_MESSAGE = "Please enter the correct username and password \ for a staff account. Note that both fields may be case-sensitive." diff -Nru python-django-1.11.9/tests/admin_widgets/models.py python-django-1.11.11/tests/admin_widgets/models.py --- python-django-1.11.9/tests/admin_widgets/models.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/admin_widgets/models.py 2018-03-06 14:07:37.000000000 +0000 @@ -1,5 +1,7 @@ from __future__ import unicode_literals +import uuid + from django.contrib.auth.models import User from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -99,6 +101,7 @@ class Honeycomb(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) location = models.CharField(max_length=20) diff -Nru python-django-1.11.9/tests/admin_widgets/tests.py python-django-1.11.11/tests/admin_widgets/tests.py --- python-django-1.11.9/tests/admin_widgets/tests.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/admin_widgets/tests.py 2018-03-06 14:07:37.000000000 +0000 @@ -17,7 +17,7 @@ from django.contrib.auth.models import User from django.core.files.storage import default_storage from django.core.files.uploadedfile import SimpleUploadedFile -from django.db.models import CharField, DateField, DateTimeField +from django.db.models import CharField, DateField, DateTimeField, UUIDField from django.test import SimpleTestCase, TestCase, override_settings from django.urls import reverse from django.utils import six, translation @@ -251,6 +251,12 @@ lookup2 = widgets.url_params_from_lookup_dict({'myfield': my_callable()}) self.assertEqual(lookup1, lookup2) + def test_label_and_url_for_value_invalid_uuid(self): + field = Bee._meta.get_field('honeycomb') + self.assertIsInstance(field.target_field, UUIDField) + widget = widgets.ForeignKeyRawIdWidget(field.remote_field, admin.site) + self.assertEqual(widget.label_and_url_for_value('invalid-uuid'), ('', '')) + class FilteredSelectMultipleWidgetTest(SimpleTestCase): def test_render(self): diff -Nru python-django-1.11.9/tests/auth_tests/test_auth_backends_deprecation.py python-django-1.11.11/tests/auth_tests/test_auth_backends_deprecation.py --- python-django-1.11.9/tests/auth_tests/test_auth_backends_deprecation.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/auth_tests/test_auth_backends_deprecation.py 2018-03-06 14:07:37.000000000 +0000 @@ -13,6 +13,18 @@ pass +class NoRequestWithKwargs: + def authenticate(self, username=None, password=None, **kwargs): + pass + + +class RequestPositionalArg: + def authenticate(self, request, username=None, password=None, **kwargs): + assert username == 'username' + assert password == 'pass' + assert request is mock_request + + class RequestNotPositionArgBackend: def authenticate(self, username=None, password=None, request=None): assert username == 'username' @@ -34,6 +46,8 @@ method without a request parameter. """ no_request_backend = '%s.NoRequestBackend' % __name__ + no_request_with_kwargs_backend = '%s.NoRequestWithKwargs' % __name__ + request_positional_arg_backend = '%s.RequestPositionalArg' % __name__ request_not_positional_backend = '%s.RequestNotPositionArgBackend' % __name__ request_not_positional_with_used_kwarg_backend = '%s.RequestNotPositionArgWithUsedKwargBackend' % __name__ @@ -79,6 +93,21 @@ "argument." % self.no_request_backend ) + @override_settings(AUTHENTICATION_BACKENDS=[no_request_with_kwargs_backend, request_positional_arg_backend]) + def test_credentials_not_mutated(self): + """ + No problem if a backend doesn't accept `request` and a later one does. + """ + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') + authenticate(mock_request, username='username', password='pass') + self.assertEqual(len(warns), 1) + self.assertEqual( + str(warns[0].message), + "In %s.authenticate(), move the `request` keyword argument to the " + "first positional argument." % self.no_request_with_kwargs_backend + ) + @override_settings(AUTHENTICATION_BACKENDS=[request_not_positional_with_used_kwarg_backend]) def test_handles_backend_in_kwargs(self): with warnings.catch_warnings(record=True) as warns: diff -Nru python-django-1.11.9/tests/auth_tests/test_forms.py python-django-1.11.11/tests/auth_tests/test_forms.py --- python-django-1.11.9/tests/auth_tests/test_forms.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/auth_tests/test_forms.py 2018-03-06 14:07:37.000000000 +0000 @@ -249,6 +249,9 @@ ) +# To verify that the login form rejects inactive users, use an authentication +# backend that allows them. +@override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.AllowAllUsersModelBackend']) class AuthenticationFormTest(TestDataMixin, TestCase): def test_invalid_username(self): @@ -278,6 +281,24 @@ self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), [force_text(form.error_messages['inactive'])]) + # Use an authentication backend that rejects inactive users. + @override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.ModelBackend']) + def test_inactive_user_incorrect_password(self): + """An invalid login doesn't leak the inactive status of a user.""" + data = { + 'username': 'inactive', + 'password': 'incorrect', + } + form = AuthenticationForm(None, data) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.non_field_errors(), [ + form.error_messages['invalid_login'] % { + 'username': User._meta.get_field('username').verbose_name + } + ] + ) + def test_login_failed(self): signal_calls = [] diff -Nru python-django-1.11.9/tests/db_functions/tests.py python-django-1.11.11/tests/db_functions/tests.py --- python-django-1.11.9/tests/db_functions/tests.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/db_functions/tests.py 2018-03-06 14:07:37.000000000 +0000 @@ -16,7 +16,6 @@ from .models import Article, Author, DecimalModel, Fan - lorem_ipsum = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.""" diff -Nru python-django-1.11.9/tests/delete_regress/models.py python-django-1.11.11/tests/delete_regress/models.py --- python-django-1.11.9/tests/delete_regress/models.py 2017-10-11 13:44:57.000000000 +0000 +++ python-django-1.11.11/tests/delete_regress/models.py 2018-03-06 13:57:56.000000000 +0000 @@ -56,6 +56,8 @@ class Researcher(models.Model): contacts = models.ManyToManyField(Contact, related_name="research_contacts") + primary_contact = models.ForeignKey(Contact, models.SET_NULL, null=True, related_name='primary_contacts') + secondary_contact = models.ForeignKey(Contact, models.SET_NULL, null=True, related_name='secondary_contacts') class Food(models.Model): diff -Nru python-django-1.11.9/tests/delete_regress/tests.py python-django-1.11.11/tests/delete_regress/tests.py --- python-django-1.11.9/tests/delete_regress/tests.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/delete_regress/tests.py 2018-03-06 14:07:37.000000000 +0000 @@ -6,7 +6,7 @@ from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature from .models import ( - Award, AwardNote, Book, Child, Eaten, Email, File, Food, FooFile, + Award, AwardNote, Book, Child, Contact, Eaten, Email, File, Food, FooFile, FooFileProxy, FooImage, FooPhoto, House, Image, Item, Location, Login, OrderedPerson, OrgUnit, Person, Photo, PlayedWith, PlayedWithNote, Policy, Researcher, Toy, Version, @@ -335,7 +335,7 @@ self.assertTrue(Login.objects.filter(pk=self.l2.pk).exists()) -class OrderedDeleteTests(TestCase): +class DeleteTests(TestCase): def test_meta_ordered_delete(self): # When a subquery is performed by deletion code, the subquery must be # cleared of all ordering. There was a but that caused _meta ordering @@ -345,3 +345,27 @@ OrderedPerson.objects.create(name='Bob', lives_in=h) OrderedPerson.objects.filter(lives_in__address='Foo').delete() self.assertEqual(OrderedPerson.objects.count(), 0) + + def test_foreign_key_delete_nullifies_correct_columns(self): + """ + With a model (Researcher) that has two foreign keys pointing to the + same model (Contact), deleting an instance of the target model + (contact1) nullifies the correct fields of Researcher. + """ + contact1 = Contact.objects.create(label='Contact 1') + contact2 = Contact.objects.create(label='Contact 2') + researcher1 = Researcher.objects.create( + primary_contact=contact1, + secondary_contact=contact2, + ) + researcher2 = Researcher.objects.create( + primary_contact=contact2, + secondary_contact=contact1, + ) + contact1.delete() + researcher1.refresh_from_db() + researcher2.refresh_from_db() + self.assertIsNone(researcher1.primary_contact) + self.assertEqual(researcher1.secondary_contact, contact2) + self.assertEqual(researcher2.primary_contact, contact2) + self.assertIsNone(researcher2.secondary_contact) diff -Nru python-django-1.11.9/tests/gis_tests/test_geoip2.py python-django-1.11.11/tests/gis_tests/test_geoip2.py --- python-django-1.11.9/tests/gis_tests/test_geoip2.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/gis_tests/test_geoip2.py 2018-03-06 14:07:37.000000000 +0000 @@ -137,10 +137,10 @@ @mock.patch('socket.gethostbyname') def test05_unicode_response(self, gethostbyname): "GeoIP strings should be properly encoded (#16553)." - gethostbyname.return_value = '194.27.42.76' + gethostbyname.return_value = '191.252.51.69' g = GeoIP2() - d = g.city("nigde.edu.tr") - self.assertEqual('Niğde', d['city']) + d = g.city('www.fasano.com.br') + self.assertEqual(d['city'], 'São José dos Campos') d = g.country('200.26.205.1') # Some databases have only unaccented countries self.assertIn(d['country_name'], ('Curaçao', 'Curacao')) diff -Nru python-django-1.11.9/tests/gis_tests/test_geoip.py python-django-1.11.11/tests/gis_tests/test_geoip.py --- python-django-1.11.9/tests/gis_tests/test_geoip.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/gis_tests/test_geoip.py 2018-03-06 14:07:37.000000000 +0000 @@ -145,7 +145,7 @@ fqdn = "messe-duesseldorf.com" if self._is_dns_available(fqdn): d = g.city(fqdn) - self.assertEqual('Düsseldorf', d['city']) + self.assertEqual('Essen', d['city']) d = g.country('200.26.205.1') # Some databases have only unaccented countries self.assertIn(d['country_name'], ('Curaçao', 'Curacao')) diff -Nru python-django-1.11.9/tests/messages_tests/urls.py python-django-1.11.11/tests/messages_tests/urls.py --- python-django-1.11.9/tests/messages_tests/urls.py 2017-10-11 13:44:57.000000000 +0000 +++ python-django-1.11.11/tests/messages_tests/urls.py 2018-03-06 13:57:56.000000000 +0000 @@ -9,7 +9,6 @@ from django.views.decorators.cache import never_cache from django.views.generic.edit import FormView - TEMPLATE = """{% if messages %}
    {% for message in messages %} diff -Nru python-django-1.11.9/tests/requirements/postgres.txt python-django-1.11.11/tests/requirements/postgres.txt --- python-django-1.11.9/tests/requirements/postgres.txt 2017-10-04 13:29:42.000000000 +0000 +++ python-django-1.11.11/tests/requirements/postgres.txt 2018-03-06 13:57:56.000000000 +0000 @@ -1 +1 @@ -psycopg2>=2.5.4 +psycopg2-binary>=2.5.4 diff -Nru python-django-1.11.9/tests/template_tests/syntax_tests/test_multiline.py python-django-1.11.11/tests/template_tests/syntax_tests/test_multiline.py --- python-django-1.11.9/tests/template_tests/syntax_tests/test_multiline.py 2017-10-11 13:44:52.000000000 +0000 +++ python-django-1.11.11/tests/template_tests/syntax_tests/test_multiline.py 2018-03-06 13:57:56.000000000 +0000 @@ -2,7 +2,6 @@ from ..utils import setup - multiline_string = """ Hello, boys. diff -Nru python-django-1.11.9/tests/utils_tests/test_html.py python-django-1.11.11/tests/utils_tests/test_html.py --- python-django-1.11.9/tests/utils_tests/test_html.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/utils_tests/test_html.py 2018-03-06 14:07:37.000000000 +0000 @@ -232,3 +232,11 @@ @html.html_safe class HtmlClass(object): pass + + def test_urlize_unchanged_inputs(self): + tests = ( + ('a' + '@a' * 50000) + 'a', # simple_email_re catastrophic test + ('a' + '.' * 1000000) + 'a', # trailing_punctuation catastrophic test + ) + for value in tests: + self.assertEqual(html.urlize(value), value) diff -Nru python-django-1.11.9/tests/utils_tests/test_text.py python-django-1.11.11/tests/utils_tests/test_text.py --- python-django-1.11.9/tests/utils_tests/test_text.py 2018-01-02 00:52:33.000000000 +0000 +++ python-django-1.11.11/tests/utils_tests/test_text.py 2018-03-06 14:07:37.000000000 +0000 @@ -139,6 +139,10 @@ truncator = text.Truncator('

    I <3 python, what about you?

    ') self.assertEqual('

    I <3 python...

    ', truncator.words(3, '...', html=True)) + re_tag_catastrophic_test = ('' + truncator = text.Truncator(re_tag_catastrophic_test) + self.assertEqual(re_tag_catastrophic_test, truncator.words(500, html=True)) + def test_wrap(self): digits = '1234 67 9' self.assertEqual(text.wrap(digits, 100), '1234 67 9')