From 5737c57d95cc8c17b1aa2da4809f70ad4c212716 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 9 Aug 2013 16:02:05 -0400 Subject: Fixed #20868 -- Added an email to django-announce as a security step. Thanks garrison for the report. --- docs/internals/security.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/internals/security.txt b/docs/internals/security.txt index 486b2c9968..327a6a5f60 100644 --- a/docs/internals/security.txt +++ b/docs/internals/security.txt @@ -108,8 +108,12 @@ On the day of disclosure, we will take the following steps: relevant patches and new releases, and crediting the reporter of the issue (if the reporter wishes to be publicly identified). +4. Post a notice to the `django-announce`_ mailing list that links to the blog + post. + .. _the Python Package Index: http://pypi.python.org/pypi .. _the official Django development blog: https://www.djangoproject.com/weblog/ +.. _django-announce: http://groups.google.com/group/django-announce If a reported issue is believed to be particularly time-sensitive -- due to a known exploit in the wild, for example -- the time between @@ -214,4 +218,4 @@ If you are added to the notification list, security-related emails will be sent to you by Django's release manager, and all notification emails will be signed with the same key used to sign Django releases; that key has the ID ``0x3684C0C08C8B2AE1``, and is available from most -commonly-used keyservers. \ No newline at end of file +commonly-used keyservers. -- cgit v1.3 From 00d23a13ebaf6057d1428e798bfb6cf47bb5ef7c Mon Sep 17 00:00:00 2001 From: ersran9 Date: Wed, 7 Aug 2013 21:33:31 +0530 Subject: Fixed #20828 -- Allowed @permission_required to take a list of permissions Thanks Giggaflop for the suggestion. --- django/contrib/auth/decorators.py | 6 ++- django/contrib/auth/tests/test_decorators.py | 58 +++++++++++++++++++++++++++- docs/releases/1.7.txt | 3 ++ docs/topics/auth/default.txt | 5 +++ 4 files changed, 70 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 11518193e7..24e31144b1 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -64,8 +64,12 @@ def permission_required(perm, login_url=None, raise_exception=False): is raised. """ def check_perms(user): + if not isinstance(perm, (list, tuple)): + perms = (perm, ) + else: + perms = perm # First check if the user has the permission (even anon users) - if user.has_perm(perm): + if user.has_perms(perms): return True # In case the 403 handler should be called raise the exception if raise_exception: diff --git a/django/contrib/auth/tests/test_decorators.py b/django/contrib/auth/tests/test_decorators.py index 6d6d335354..22ad933644 100644 --- a/django/contrib/auth/tests/test_decorators.py +++ b/django/contrib/auth/tests/test_decorators.py @@ -1,7 +1,12 @@ from django.conf import settings -from django.contrib.auth.decorators import login_required +from django.contrib.auth import models +from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.tests.test_views import AuthViewsTestCase from django.contrib.auth.tests.utils import skipIfCustomUser +from django.core.exceptions import PermissionDenied +from django.http import HttpResponse +from django.test import TestCase +from django.test.client import RequestFactory @skipIfCustomUser @@ -49,3 +54,54 @@ class LoginRequiredTestCase(AuthViewsTestCase): """ self.testLoginRequired(view_url='/login_required_login_url/', login_url='/somewhere/') + + +class PermissionsRequiredDecoratorTest(TestCase): + """ + Tests for the permission_required decorator + """ + def setUp(self): + self.user = models.User.objects.create(username='joe', password='qwerty') + self.factory = RequestFactory() + # Add permissions auth.add_customuser and auth.change_customuser + perms = models.Permission.objects.filter(codename__in=('add_customuser', 'change_customuser')) + self.user.user_permissions.add(*perms) + + def test_many_permissions_pass(self): + + @permission_required(['auth.add_customuser', 'auth.change_customuser']) + def a_view(request): + return HttpResponse() + request = self.factory.get('/rand') + request.user = self.user + resp = a_view(request) + self.assertEqual(resp.status_code, 200) + + def test_single_permission_pass(self): + + @permission_required('auth.add_customuser') + def a_view(request): + return HttpResponse() + request = self.factory.get('/rand') + request.user = self.user + resp = a_view(request) + self.assertEqual(resp.status_code, 200) + + def test_permissioned_denied_redirect(self): + + @permission_required(['auth.add_customuser', 'auth.change_customuser', 'non-existant-permission']) + def a_view(request): + return HttpResponse() + request = self.factory.get('/rand') + request.user = self.user + resp = a_view(request) + self.assertEqual(resp.status_code, 302) + + def test_permissioned_denied_exception_raised(self): + + @permission_required(['auth.add_customuser', 'auth.change_customuser', 'non-existant-permission'], raise_exception=True) + def a_view(request): + return HttpResponse() + request = self.factory.get('/rand') + request.user = self.user + self.assertRaises(PermissionDenied, a_view, request) diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 6c28d6e1d0..ec37b382d4 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -135,6 +135,9 @@ Minor features ``Meta`` option allows you to customize (or disable) creation of the default add, change, and delete permissions. +* The :func:`~django.contrib.auth.decorators.permission_required` decorator can + take a list of permissions as well as a single permission. + Backwards incompatible changes in 1.7 ===================================== diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 7dff9cdca7..78bf820e89 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -528,6 +528,11 @@ The permission_required decorator (HTTP Forbidden) view` instead of redirecting to the login page. + .. versionchanged:: 1.7 + + The :func:`~django.contrib.auth.decorators.permission_required` + decorator can take a list of permissions as well as a single permission. + Applying permissions to generic views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.3 From e868eaf680a3d7acfcd3c76743bb248d29ac7b60 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Sat, 10 Aug 2013 22:24:24 +0100 Subject: clarified misleading wording about squashing commits --- docs/internals/contributing/writing-code/working-with-git.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/internals/contributing/writing-code/working-with-git.txt b/docs/internals/contributing/writing-code/working-with-git.txt index dcfdd9e85b..32fc459e70 100644 --- a/docs/internals/contributing/writing-code/working-with-git.txt +++ b/docs/internals/contributing/writing-code/working-with-git.txt @@ -157,7 +157,7 @@ using interactive rebase:: The HEAD~2 above is shorthand for two latest commits. The above command will open an editor showing the two commits, prefixed with the word "pick". -Change the second line to "squash" instead. This will keep the +Change "pick" on the second line to "squash" instead. This will keep the first commit, and squash the second commit into the first one. Save and quit the editor. A second editor window should open, so you can reword the commit message for the commit now that it includes both your steps. -- cgit v1.3 From ab680725bfb2f0d79cff26331b30a3d583c55a80 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 10 Aug 2013 18:08:05 -0400 Subject: Fixed #20890 -- Added missing import in class-based view docs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks André Augusto. --- docs/topics/class-based-views/intro.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'docs') diff --git a/docs/topics/class-based-views/intro.txt b/docs/topics/class-based-views/intro.txt index a65b887921..5986ff2ea7 100644 --- a/docs/topics/class-based-views/intro.txt +++ b/docs/topics/class-based-views/intro.txt @@ -198,6 +198,7 @@ A similar class-based view might look like:: from django.http import HttpResponseRedirect from django.shortcuts import render + from django.views.generic.base import View from .forms import MyForm -- cgit v1.3 From 6bdb3b1135d1bd7b2dc24131b9d26ac19ebdba67 Mon Sep 17 00:00:00 2001 From: Mel Collins Date: Mon, 13 May 2013 13:38:53 +0200 Subject: Fixed #13518 -- Added FILE_UPLOAD_DIRECTORY_PERMISSIONS setting This setting does for new directories what FILE_UPLOAD_PERMISSIONS does for new files. Thanks jacob@ for the suggestion. --- django/conf/global_settings.py | 5 +++++ django/core/files/storage.py | 11 ++++++++++- docs/ref/settings.txt | 13 +++++++++++++ docs/releases/1.7.txt | 4 ++++ docs/topics/http/file-uploads.txt | 7 ++++++- tests/file_storage/tests.py | 12 ++++++++++++ 6 files changed, 50 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 19258fbcd4..95deaa8d87 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -313,6 +313,11 @@ FILE_UPLOAD_TEMP_DIR = None # you'd pass directly to os.chmod; see http://docs.python.org/lib/os-file-dir.html. FILE_UPLOAD_PERMISSIONS = None +# The numeric mode to assign to newly-created directories, when uploading files. +# The value should be a mode as you'd pass to os.chmod; +# see http://docs.python.org/lib/os-file-dir.html. +FILE_UPLOAD_DIRECTORY_PERMISSIONS = None + # Python module path where user will place custom format definition. # The directory where this setting is pointing should contain subdirectories # named as the locales, containing a formats.py file diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 5d301a317c..5e587da2da 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -172,7 +172,16 @@ class FileSystemStorage(Storage): directory = os.path.dirname(full_path) if not os.path.exists(directory): try: - os.makedirs(directory) + if settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS is not None: + # os.makedirs applies the global umask, so we reset it, + # for consistency with FILE_UPLOAD_PERMISSIONS behavior. + old_umask = os.umask(0) + try: + os.makedirs(directory, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS) + finally: + os.umask(old_umask) + else: + os.makedirs(directory) except OSError as e: if e.errno != errno.EEXIST: raise diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 38d7275aed..90545d96c5 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1108,6 +1108,19 @@ Default: ``2621440`` (i.e. 2.5 MB). The maximum size (in bytes) that an upload will be before it gets streamed to the file system. See :doc:`/topics/files` for details. +.. setting:: FILE_UPLOAD_DIRECTORY_PERMISSIONS + +FILE_UPLOAD_DIRECTORY_PERMISSIONS +--------------------------------- + +.. versionadded:: 1.7 + +Default: ``None`` + +The numeric mode to apply to directories created in the process of +uploading files. This value mirrors the functionality and caveats of +the :setting:`FILE_UPLOAD_PERMISSIONS` setting. + .. setting:: FILE_UPLOAD_PERMISSIONS FILE_UPLOAD_PERMISSIONS diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index ec37b382d4..6fda83ebc7 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -138,6 +138,10 @@ Minor features * The :func:`~django.contrib.auth.decorators.permission_required` decorator can take a list of permissions as well as a single permission. +* The new :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` setting controls + the file system permissions of directories created during file upload, like + :setting:`FILE_UPLOAD_PERMISSIONS` does for the files themselves. + Backwards incompatible changes in 1.7 ===================================== diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt index 2cdab9ea9b..d88524ee20 100644 --- a/docs/topics/http/file-uploads.txt +++ b/docs/topics/http/file-uploads.txt @@ -132,7 +132,7 @@ upload behavior. Changing upload handler behavior -------------------------------- -Three settings control Django's file upload behavior: +There are a few settings which control Django's file upload behavior: :setting:`FILE_UPLOAD_MAX_MEMORY_SIZE` The maximum size, in bytes, for files that will be uploaded into memory. @@ -167,6 +167,11 @@ Three settings control Django's file upload behavior: **Always prefix the mode with a 0.** +:setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` + The numeric mode to apply to directories created in the process of + uploading files. This value mirrors the functionality and caveats of + the :setting:`FILE_UPLOAD_PERMISSIONS` setting. + :setting:`FILE_UPLOAD_HANDLERS` The actual handlers for uploaded files. Changing this setting allows complete customization -- even replacement -- of Django's upload diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py index cdd9720374..2a6f602e3e 100644 --- a/tests/file_storage/tests.py +++ b/tests/file_storage/tests.py @@ -462,6 +462,18 @@ class FileStoragePermissions(unittest.TestCase): mode = os.stat(self.storage.path(fname))[0] & 0o777 self.assertEqual(mode, 0o666 & ~self.umask) + @override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765) + def test_file_upload_directory_permissions(self): + name = self.storage.save("the_directory/the_file", ContentFile("data")) + dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777 + self.assertEqual(dir_mode, 0o765) + + @override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None) + def test_file_upload_directory_default_permissions(self): + name = self.storage.save("the_directory/the_file", ContentFile("data")) + dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777 + self.assertEqual(dir_mode, 0o777 & ~self.umask) + class FileStoragePathParsing(unittest.TestCase): def setUp(self): self.storage_dir = tempfile.mkdtemp() -- cgit v1.3 From 3f6cc33cffa5774beedf7997354fc269497a93dd Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 12 Aug 2013 13:20:58 -0400 Subject: Added missing release notes for older versions of Django --- docs/releases/1.3.3.txt | 11 +++++++ docs/releases/1.3.4.txt | 37 +++++++++++++++++++++ docs/releases/1.3.5.txt | 60 +++++++++++++++++++++++++++++++++ docs/releases/1.3.6.txt | 78 +++++++++++++++++++++++++++++++++++++++++++ docs/releases/1.3.7.txt | 13 ++++++++ docs/releases/1.4.2.txt | 9 ++--- docs/releases/1.4.3.txt | 60 +++++++++++++++++++++++++++++++++ docs/releases/1.4.4.txt | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/releases/1.4.5.txt | 13 ++++++++ docs/releases/index.txt | 8 +++++ 10 files changed, 373 insertions(+), 4 deletions(-) create mode 100644 docs/releases/1.3.3.txt create mode 100644 docs/releases/1.3.4.txt create mode 100644 docs/releases/1.3.5.txt create mode 100644 docs/releases/1.3.6.txt create mode 100644 docs/releases/1.3.7.txt create mode 100644 docs/releases/1.4.3.txt create mode 100644 docs/releases/1.4.4.txt create mode 100644 docs/releases/1.4.5.txt (limited to 'docs') diff --git a/docs/releases/1.3.3.txt b/docs/releases/1.3.3.txt new file mode 100644 index 0000000000..437cbfb412 --- /dev/null +++ b/docs/releases/1.3.3.txt @@ -0,0 +1,11 @@ +========================== +Django 1.3.3 release notes +========================== + +*August 1, 2012* + +Following Monday's security release of :doc:`Django 1.3.2 `, +we began receiving reports that one of the fixes applied was breaking Python +2.4 compatibility for Django 1.3. Since Python 2.4 is a supported Python +version for that release series, this release fixes compatibility with +Python 2.4. diff --git a/docs/releases/1.3.4.txt b/docs/releases/1.3.4.txt new file mode 100644 index 0000000000..3a174b3d44 --- /dev/null +++ b/docs/releases/1.3.4.txt @@ -0,0 +1,37 @@ +========================== +Django 1.3.4 release notes +========================== + +*October 17, 2012* + +This is the fourth release in the Django 1.3 series. + +Host header poisoning +--------------------- + +Some parts of Django -- independent of end-user-written applications -- make +use of full URLs, including domain name, which are generated from the HTTP Host +header. Some attacks against this are beyond Django's ability to control, and +require the web server to be properly configured; Django's documentation has +for some time contained notes advising users on such configuration. + +Django's own built-in parsing of the Host header is, however, still vulnerable, +as was reported to us recently. The Host header parsing in Django 1.3.3 and +Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was +incorrectly handling username/password information in the header. Thus, for +example, the following Host header would be accepted by Django when running on +"validsite.com":: + + Host: validsite.com:random@evilsite.com + +Using this, an attacker can cause parts of Django -- particularly the +password-reset mechanism -- to generate and display arbitrary URLs to users. + +To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified; +Host headers which contain potentially dangerous content (such as +username/password pairs) now raise the exception +:exc:`django.core.exceptions.SuspiciousOperation`. + +Details of this issue were initially posted online as a `security advisory`_. + +.. _security advisory: https://www.djangoproject.com/weblog/2012/oct/17/security/ diff --git a/docs/releases/1.3.5.txt b/docs/releases/1.3.5.txt new file mode 100644 index 0000000000..65c403209d --- /dev/null +++ b/docs/releases/1.3.5.txt @@ -0,0 +1,60 @@ +========================== +Django 1.3.5 release notes +========================== + +*December 10, 2012* + +Django 1.3.5 addresses two security issues present in previous Django releases +in the 1.3 series. + +Please be aware that this security release is slightly different from previous +ones. Both issues addressed here have been dealt with in prior security updates +to Django. In one case, we have received ongoing reports of problems, and in +the other we've chosen to take further steps to tighten up Django's code in +response to independent discovery of potential problems from multiple sources. + +Host header poisoning +--------------------- + +Several earlier Django security releases focused on the issue of poisoning the +HTTP Host header, causing Django to generate URLs pointing to arbitrary, +potentially-malicious domains. + +In response to further input received and reports of continuing issues +following the previous release, we're taking additional steps to tighten Host +header validation. Rather than attempt to accommodate all features HTTP +supports here, Django's Host header validation attempts to support a smaller, +but far more common, subset: + +* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot + ('.'). +* IP addresses -- both IPv4 and IPv6 -- are permitted. +* Port, if specified, is numeric. + +Any deviation from this will now be rejected, raising the exception +:exc:`django.core.exceptions.SuspiciousOperation`. + +Redirect poisoning +------------------ + +Also following up on a previous issue: in July of this year, we made changes to +Django's HTTP redirect classes, performing additional validation of the scheme +of the URL to redirect to (since, both within Django's own supplied +applications and many third-party applications, accepting a user-supplied +redirect target is a common pattern). + +Since then, two independent audits of the code turned up further potential +problems. So, similar to the Host-header issue, we are taking steps to provide +tighter validation in response to reported problems (primarily with third-party +applications, but to a certain extent also within Django itself). This comes in +two parts: + +1. A new utility function, ``django.utils.http.is_safe_url``, is added; this +function takes a URL and a hostname, and checks that the URL is either +relative, or if absolute matches the supplied hostname. This function is +intended for use whenever user-supplied redirect targets are accepted, to +ensure that such redirects cannot lead to arbitrary third-party sites. + +2. All of Django's own built-in views -- primarily in the authentication system +-- which allow user-supplied redirect targets now use ``is_safe_url`` to +validate the supplied URL. diff --git a/docs/releases/1.3.6.txt b/docs/releases/1.3.6.txt new file mode 100644 index 0000000000..d55199a882 --- /dev/null +++ b/docs/releases/1.3.6.txt @@ -0,0 +1,78 @@ +========================== +Django 1.3.6 release notes +========================== + +*February 19, 2013* + +Django 1.3.6 fixes four security issues present in previous Django releases in +the 1.3 series. + +This is the sixth bugfix/security release in the Django 1.3 series. + + +Host header poisoning +--------------------- + +Some parts of Django -- independent of end-user-written applications -- make +use of full URLs, including domain name, which are generated from the HTTP Host +header. Django's documentation has for some time contained notes advising users +on how to configure webservers to ensure that only valid Host headers can reach +the Django application. However, it has been reported to us that even with the +recommended webserver configurations there are still techniques available for +tricking many common webservers into supplying the application with an +incorrect and possibly malicious Host header. + +For this reason, Django 1.3.6 adds a new setting, ``ALLOWED_HOSTS``, which +should contain an explicit list of valid host/domain names for this site. A +request with a Host header not matching an entry in this list will raise +``SuspiciousOperation`` if ``request.get_host()`` is called. For full details +see the documentation for the :setting:`ALLOWED_HOSTS` setting. + +The default value for this setting in Django 1.3.6 is ``['*']`` (matching any +host), for backwards-compatibility, but we strongly encourage all sites to set +a more restrictive value. + +This host validation is disabled when ``DEBUG`` is ``True`` or when running tests. + + +XML deserialization +------------------- + +The XML parser in the Python standard library is vulnerable to a number of +attacks via external entities and entity expansion. Django uses this parser for +deserializing XML-formatted database fixtures. The fixture deserializer is not +intended for use with untrusted data, but in order to err on the side of safety +in Django 1.3.6 the XML deserializer refuses to parse an XML document with a +DTD (DOCTYPE definition), which closes off these attack avenues. + +These issues in the Python standard library are CVE-2013-1664 and +CVE-2013-1665. More information available `from the Python security team`_. + +Django's XML serializer does not create documents with a DTD, so this should +not cause any issues with the typical round-trip from ``dumpdata`` to +``loaddata``, but if you feed your own XML documents to the ``loaddata`` +management command, you will need to ensure they do not contain a DTD. + +.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html + + +Formset memory exhaustion +------------------------- + +Previous versions of Django did not validate or limit the form-count data +provided by the client in a formset's management form, making it possible to +exhaust a server's available memory by forcing it to create very large numbers +of forms. + +In Django 1.3.6, all formsets have a strictly-enforced maximum number of forms +(1000 by default, though it can be set higher via the ``max_num`` formset +factory argument). + + +Admin history view information leakage +-------------------------------------- + +In previous versions of Django, an admin user without change permission on a +model could still view the unicode representation of instances via their admin +history log. Django 1.3.6 now limits the admin history log view for an object +to users with change permission for that model. diff --git a/docs/releases/1.3.7.txt b/docs/releases/1.3.7.txt new file mode 100644 index 0000000000..3cccfcfb1c --- /dev/null +++ b/docs/releases/1.3.7.txt @@ -0,0 +1,13 @@ +========================== +Django 1.3.7 release notes +========================== + +*February 20, 2013* + +Django 1.3.7 corrects a packaging problem with yesterday's :doc:`1.3.6 release +`. + +The release contained stray ``.pyc`` files that caused "bad magic number" +errors when running with some versions of Python. This releases corrects this, +and also fixes a bad documentation link in the project template ``settings.py`` +file generated by ``manage.py startproject``. diff --git a/docs/releases/1.4.2.txt b/docs/releases/1.4.2.txt index 07eec39764..a6150f56c3 100644 --- a/docs/releases/1.4.2.txt +++ b/docs/releases/1.4.2.txt @@ -17,7 +17,7 @@ for some time contained notes advising users on such configuration. Django's own built-in parsing of the Host header is, however, still vulnerable, as was reported to us recently. The Host header parsing in Django 1.3.3 and -Django 1.4.1 -- specifically, django.http.HttpRequest.get_host() -- was +Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was incorrectly handling username/password information in the header. Thus, for example, the following Host header would be accepted by Django when running on "validsite.com":: @@ -27,9 +27,10 @@ example, the following Host header would be accepted by Django when running on Using this, an attacker can cause parts of Django -- particularly the password-reset mechanism -- to generate and display arbitrary URLs to users. -To remedy this, the parsing in HttpRequest.get_host() is being modified; Host -headers which contain potentially dangerous content (such as username/password -pairs) now raise the exception django.core.exceptions.SuspiciousOperation +To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified; +Host headers which contain potentially dangerous content (such as +username/password pairs) now raise the exception +:exc:`django.core.exceptions.SuspiciousOperation`. Details of this issue were initially posted online as a `security advisory`_. diff --git a/docs/releases/1.4.3.txt b/docs/releases/1.4.3.txt new file mode 100644 index 0000000000..aadf623c3c --- /dev/null +++ b/docs/releases/1.4.3.txt @@ -0,0 +1,60 @@ +========================== +Django 1.4.3 release notes +========================== + +*December 10, 2012* + +Django 1.4.3 addresses two security issues present in previous Django releases +in the 1.4 series. + +Please be aware that this security release is slightly different from previous +ones. Both issues addressed here have been dealt with in prior security updates +to Django. In one case, we have received ongoing reports of problems, and in +the other we've chosen to take further steps to tighten up Django's code in +response to independent discovery of potential problems from multiple sources. + +Host header poisoning +--------------------- + +Several earlier Django security releases focused on the issue of poisoning the +HTTP Host header, causing Django to generate URLs pointing to arbitrary, +potentially-malicious domains. + +In response to further input received and reports of continuing issues +following the previous release, we're taking additional steps to tighten Host +header validation. Rather than attempt to accommodate all features HTTP +supports here, Django's Host header validation attempts to support a smaller, +but far more common, subset: + +* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot + ('.'). +* IP addresses -- both IPv4 and IPv6 -- are permitted. +* Port, if specified, is numeric. + +Any deviation from this will now be rejected, raising the exception +:exc:`django.core.exceptions.SuspiciousOperation`. + +Redirect poisoning +------------------ + +Also following up on a previous issue: in July of this year, we made changes to +Django's HTTP redirect classes, performing additional validation of the scheme +of the URL to redirect to (since, both within Django's own supplied +applications and many third-party applications, accepting a user-supplied +redirect target is a common pattern). + +Since then, two independent audits of the code turned up further potential +problems. So, similar to the Host-header issue, we are taking steps to provide +tighter validation in response to reported problems (primarily with third-party +applications, but to a certain extent also within Django itself). This comes in +two parts: + +1. A new utility function, ``django.utils.http.is_safe_url``, is added; this +function takes a URL and a hostname, and checks that the URL is either +relative, or if absolute matches the supplied hostname. This function is +intended for use whenever user-supplied redirect targets are accepted, to +ensure that such redirects cannot lead to arbitrary third-party sites. + +2. All of Django's own built-in views -- primarily in the authentication system +-- which allow user-supplied redirect targets now use ``is_safe_url`` to +validate the supplied URL. diff --git a/docs/releases/1.4.4.txt b/docs/releases/1.4.4.txt new file mode 100644 index 0000000000..c5fcbc3e39 --- /dev/null +++ b/docs/releases/1.4.4.txt @@ -0,0 +1,88 @@ +========================== +Django 1.4.4 release notes +========================== + +*February 19, 2013* + +Django 1.4.4 fixes four security issues present in previous Django releases in +the 1.4 series, as well as several other bugs and numerous documentation +improvements. + +This is the fourth bugfix/security release in the Django 1.4 series. + + +Host header poisoning +--------------------- + +Some parts of Django -- independent of end-user-written applications -- make +use of full URLs, including domain name, which are generated from the HTTP Host +header. Django's documentation has for some time contained notes advising users +on how to configure webservers to ensure that only valid Host headers can reach +the Django application. However, it has been reported to us that even with the +recommended webserver configurations there are still techniques available for +tricking many common webservers into supplying the application with an +incorrect and possibly malicious Host header. + +For this reason, Django 1.4.4 adds a new setting, ``ALLOWED_HOSTS``, containing +an explicit list of valid host/domain names for this site. A request with a +Host header not matching an entry in this list will raise +``SuspiciousOperation`` if ``request.get_host()`` is called. For full details +see the documentation for the :setting:`ALLOWED_HOSTS` setting. + +The default value for this setting in Django 1.4.4 is ``['*']`` (matching any +host), for backwards-compatibility, but we strongly encourage all sites to set +a more restrictive value. + +This host validation is disabled when ``DEBUG`` is ``True`` or when running tests. + + +XML deserialization +------------------- + +The XML parser in the Python standard library is vulnerable to a number of +attacks via external entities and entity expansion. Django uses this parser for +deserializing XML-formatted database fixtures. This deserializer is not +intended for use with untrusted data, but in order to err on the side of safety +in Django 1.4.4 the XML deserializer refuses to parse an XML document with a +DTD (DOCTYPE definition), which closes off these attack avenues. + +These issues in the Python standard library are CVE-2013-1664 and +CVE-2013-1665. More information available `from the Python security team`_. + +Django's XML serializer does not create documents with a DTD, so this should +not cause any issues with the typical round-trip from ``dumpdata`` to +``loaddata``, but if you feed your own XML documents to the ``loaddata`` +management command, you will need to ensure they do not contain a DTD. + +.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html + + +Formset memory exhaustion +------------------------- + +Previous versions of Django did not validate or limit the form-count data +provided by the client in a formset's management form, making it possible to +exhaust a server's available memory by forcing it to create very large numbers +of forms. + +In Django 1.4.4, all formsets have a strictly-enforced maximum number of forms +(1000 by default, though it can be set higher via the ``max_num`` formset +factory argument). + + +Admin history view information leakage +-------------------------------------- + +In previous versions of Django, an admin user without change permission on a +model could still view the unicode representation of instances via their admin +history log. Django 1.4.4 now limits the admin history log view for an object +to users with change permission for that model. + + +Other bugfixes and changes +========================== + +* Prevented transaction state from leaking from one request to the next (#19707). +* Changed a SQL command syntax to be MySQL 4 compatible (#19702). +* Added backwards-compatibility with old unsalted MD5 passwords (#18144). +* Numerous documentation improvements and fixes. diff --git a/docs/releases/1.4.5.txt b/docs/releases/1.4.5.txt new file mode 100644 index 0000000000..9ba5235f79 --- /dev/null +++ b/docs/releases/1.4.5.txt @@ -0,0 +1,13 @@ +========================== +Django 1.4.5 release notes +========================== + +*February 20, 2013* + +Django 1.4.5 corrects a packaging problem with yesterday's :doc:`1.4.4 release +`. + +The release contained stray ``.pyc`` files that caused "bad magic number" +errors when running with some versions of Python. This releases corrects this, +and also fixes a bad documentation link in the project template ``settings.py`` +file generated by ``manage.py startproject``. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 39439ff9aa..98598209cf 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -44,6 +44,9 @@ Final releases .. toctree:: :maxdepth: 1 + 1.4.5 + 1.4.4 + 1.4.3 1.4.2 1.4.1 1.4 @@ -53,6 +56,11 @@ Final releases .. toctree:: :maxdepth: 1 + 1.3.7 + 1.3.6 + 1.3.5 + 1.3.4 + 1.3.3 1.3.2 1.3.1 1.3 -- cgit v1.3 From 163a34ce4bc1086b346a52c7271f48d2c207f710 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Mon, 12 Aug 2013 18:30:38 +0700 Subject: Fixed #20883 -- Made model inheritance find parent links in abstract parents --- django/db/models/base.py | 21 ++++++++++++++++----- docs/releases/1.7.txt | 3 +++ tests/model_inheritance_regress/models.py | 13 +++++++++++++ tests/model_inheritance_regress/tests.py | 16 +++++++++++++++- 4 files changed, 47 insertions(+), 6 deletions(-) (limited to 'docs') diff --git a/django/db/models/base.py b/django/db/models/base.py index 01ba559e08..cd3768bccb 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -184,10 +184,21 @@ class ModelBase(type): else: new_class._meta.concrete_model = new_class - # Do the appropriate setup for any model parents. - o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields - if isinstance(f, OneToOneField)]) + # Collect the parent links for multi-table inheritance. + parent_links = {} + for base in reversed([new_class] + parents): + # Conceptually equivalent to `if base is Model`. + if not hasattr(base, '_meta'): + continue + # Skip concrete parent classes. + if base != new_class and not base._meta.abstract: + continue + # Locate OneToOneField instances. + for field in base._meta.local_fields: + if isinstance(field, OneToOneField): + parent_links[field.rel.to] = field + # Do the appropriate setup for any model parents. for base in parents: original_base = base if not hasattr(base, '_meta'): @@ -208,8 +219,8 @@ class ModelBase(type): if not base._meta.abstract: # Concrete classes... base = base._meta.concrete_model - if base in o2o_map: - field = o2o_map[base] + if base in parent_links: + field = parent_links[base] elif not is_proxy: attr_name = '%s_ptr' % base._meta.model_name field = OneToOneField(base, name=attr_name, diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 6fda83ebc7..6ea957d959 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -142,6 +142,9 @@ Minor features the file system permissions of directories created during file upload, like :setting:`FILE_UPLOAD_PERMISSIONS` does for the files themselves. +* Explicit :class:`~django.db.models.OneToOneField` for + :ref:`multi-table-inheritance` are now discovered in abstract classes. + Backwards incompatible changes in 1.7 ===================================== diff --git a/tests/model_inheritance_regress/models.py b/tests/model_inheritance_regress/models.py index 811c8175bb..0f45a2cb3f 100644 --- a/tests/model_inheritance_regress/models.py +++ b/tests/model_inheritance_regress/models.py @@ -50,6 +50,19 @@ class ParkingLot3(Place): primary_key = models.AutoField(primary_key=True) parent = models.OneToOneField(Place, parent_link=True) +class ParkingLot4(models.Model): + # Test parent_link connector can be discovered in abstract classes. + parent = models.OneToOneField(Place, parent_link=True) + + class Meta: + abstract = True + +class ParkingLot4A(ParkingLot4, Place): + pass + +class ParkingLot4B(Place, ParkingLot4): + pass + class Supplier(models.Model): restaurant = models.ForeignKey(Restaurant) diff --git a/tests/model_inheritance_regress/tests.py b/tests/model_inheritance_regress/tests.py index 10a1230685..7f78fc7a98 100644 --- a/tests/model_inheritance_regress/tests.py +++ b/tests/model_inheritance_regress/tests.py @@ -14,7 +14,8 @@ from .models import (Place, Restaurant, ItalianRestaurant, ParkingLot, ParkingLot2, ParkingLot3, Supplier, Wholesaler, Child, SelfRefParent, SelfRefChild, ArticleWithAuthor, M2MChild, QualityControl, DerivedM, Person, BirthdayParty, BachelorParty, MessyBachelorParty, - InternalCertificationAudit, BusStation, TrainStation, User, Profile) + InternalCertificationAudit, BusStation, TrainStation, User, Profile, + ParkingLot4A, ParkingLot4B) class ModelInheritanceTest(TestCase): @@ -311,6 +312,19 @@ class ModelInheritanceTest(TestCase): ParkingLot3._meta.get_ancestor_link(Place).name, "parent") + def test_use_explicit_o2o_to_parent_from_abstract_model(self): + self.assertEqual(ParkingLot4A._meta.pk.name, "parent") + ParkingLot4A.objects.create( + name="Parking4A", + address='21 Jump Street', + ) + + self.assertEqual(ParkingLot4B._meta.pk.name, "parent") + ParkingLot4A.objects.create( + name="Parking4B", + address='21 Jump Street', + ) + def test_all_fields_from_abstract_base_class(self): """ Regression tests for #7588 -- cgit v1.3 From db682dcc9e028fa40bb4d3efb322fd3191ed1bd2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 13 Aug 2013 11:16:30 -0500 Subject: Added 1.4.6/1.5.2 release notes. --- docs/releases/1.4.6.txt | 31 +++++++++++++++++++++++++ docs/releases/1.5.2.txt | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/releases/index.txt | 2 ++ 3 files changed, 95 insertions(+) create mode 100644 docs/releases/1.4.6.txt create mode 100644 docs/releases/1.5.2.txt (limited to 'docs') diff --git a/docs/releases/1.4.6.txt b/docs/releases/1.4.6.txt new file mode 100644 index 0000000000..575e9fa75a --- /dev/null +++ b/docs/releases/1.4.6.txt @@ -0,0 +1,31 @@ +========================== +Django 1.4.6 release notes +========================== + +*August 13, 2013* + +Django 1.4.6 fixes one security issue present in previous Django releases in +the 1.4 series, as well as one other bug. + +This is the sixth bugfix/security release in the Django 1.4 series. + +Mitigated possible XSS attack via user-supplied redirect URLs +------------------------------------------------------------- + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login`, :mod:`django.contrib.comments`, and +:doc:`i18n `) to redirect the user to an "on success" URL. +The security checks for these redirects (namely +``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)`` +and as such allowed ``javascript:...`` URLs to be entered. If a developer +relied on ``is_safe_url()`` to provide safe redirect targets and put such a +URL into a link, he could suffer from a XSS attack. This bug doesn't affect +Django currently, since we only put this URL into the ``Location`` response +header and browsers seem to ignore JavaScript there. + +Bugfixes +======== + +* Fixed an obscure bug with the :func:`~django.test.utils.override_settings` + decorator. If you hit an ``AttributeError: 'Settings' object has no attribute + '_original_allowed_hosts'`` exception, it's probably fixed (#20636). diff --git a/docs/releases/1.5.2.txt b/docs/releases/1.5.2.txt new file mode 100644 index 0000000000..710f16555c --- /dev/null +++ b/docs/releases/1.5.2.txt @@ -0,0 +1,62 @@ +========================== +Django 1.5.2 release notes +========================== + +*August 13, 2013* + +This is Django 1.5.2, a bugfix and security release for Django 1.5. + +Mitigated possible XSS attack via user-supplied redirect URLs +------------------------------------------------------------- + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login`, :mod:`django.contrib.comments`, and +:doc:`i18n `) to redirect the user to an "on success" URL. +The security checks for these redirects (namely +``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)`` +and as such allowed ``javascript:...`` URLs to be entered. If a developer +relied on ``is_safe_url()`` to provide safe redirect targets and put such a +URL into a link, he could suffer from a XSS attack. This bug doesn't affect +Django currently, since we only put this URL into the ``Location`` response +header and browsers seem to ignore JavaScript there. + +XSS vulnerability in :mod:`django.contrib.admin` +------------------------------------------------ + +If a :class:`~django.db.models.URLField` is used in Django 1.5, it displays the +current value of the field and a link to the target on the admin change page. +The display routine of this widget was flawed and allowed for XSS. + +Bugfixes +======== + +* Fixed a crash with :meth:`~django.db.models.query.QuerySet.prefetch_related` + (#19607) as well as some ``pickle`` regressions with ``prefetch_related`` + (#20157 and #20257). +* Fixed a regression in :mod:`django.contrib.gis` in the Google Map output on + Python 3 (#20773). +* Made ``DjangoTestSuiteRunner.setup_databases`` properly handle aliases for + the default database (#19940) and prevented ``teardown_databases`` from + attempting to tear down aliases (#20681). +* Fixed the ``django.core.cache.backends.memcached.MemcachedCache`` backend's + ``get_many()`` method on Python 3 (#20722). +* Fixed :mod:`django.contrib.humanize` translation syntax errors. Affected + languages: Mexican Spanish, Mongolian, Romanian, Turkish (#20695). +* Added support for wheel packages (#19252). +* The CSRF token now rotates when a user logs in. +* Some Python 3 compatibility fixes including #20212 and #20025. +* Fixed some rare cases where :meth:`~django.db.models.query.QuerySet.get` + exceptions recursed infinitely (#20278). +* :djadmin:`makemessages` no longer crashes with ``UnicodeDecodeError`` + (#20354). +* Fixed ``geojson`` detection with Spatialite. +* :meth:`~django.test.SimpleTestCase.assertContains` once again works with + binary content (#20237). +* Fixed :class:`~django.db.models.ManyToManyField` if it has a unicode ``name`` + parameter (#20207). +* Ensured that the WSGI request's path is correctly based on the + ``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME` + setting, regardless of whether or not either has a trailing slash (#20169). +* Fixed an obscure bug with the :func:`~django.test.utils.override_settings` + decorator. If you hit an ``AttributeError: 'Settings' object has no attribute + '_original_allowed_hosts'`` exception, it's probably fixed (#20636). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 98598209cf..c3ba5cf478 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -36,6 +36,7 @@ Final releases .. toctree:: :maxdepth: 1 + 1.5.2 1.5.1 1.5 @@ -44,6 +45,7 @@ Final releases .. toctree:: :maxdepth: 1 + 1.4.6 1.4.5 1.4.4 1.4.3 -- cgit v1.3 From 907ef9d0d157c47c66bf265dca93a0bee8664ea3 Mon Sep 17 00:00:00 2001 From: Matt Johnson Date: Tue, 4 Jun 2013 22:41:49 +0200 Subject: Fixed #20555 -- Make subwidget id attribute available In `BoundField.__iter__`, the widget's id attribute is now passed to each subwidget. A new id_for_label property was added to ChoiceInput. --- django/forms/forms.py | 4 +- django/forms/widgets.py | 15 ++++---- docs/ref/forms/widgets.txt | 65 +++++++++++++++++++++------------ docs/releases/1.7.txt | 7 ++++ tests/forms_tests/tests/test_widgets.py | 16 ++++++++ 5 files changed, 75 insertions(+), 32 deletions(-) (limited to 'docs') diff --git a/django/forms/forms.py b/django/forms/forms.py index c2b700ce77..ec51507981 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -434,7 +434,9 @@ class BoundField(object): This really is only useful for RadioSelect widgets, so that you can iterate over individual radio buttons in a template. """ - for subwidget in self.field.widget.subwidgets(self.html_name, self.value()): + id_ = self.field.widget.attrs.get('id') or self.auto_id + attrs = {'id': id_} if id_ else {} + for subwidget in self.field.widget.subwidgets(self.html_name, self.value(), attrs): yield subwidget def __len__(self): diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 0a5059a9c2..98d47d0b00 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -601,16 +601,15 @@ class ChoiceInput(SubWidget): self.choice_value = force_text(choice[0]) self.choice_label = force_text(choice[1]) self.index = index + if 'id' in self.attrs: + self.attrs['id'] += "_%d" % self.index def __str__(self): return self.render() def render(self, name=None, value=None, attrs=None, choices=()): - name = name or self.name - value = value or self.value - attrs = attrs or self.attrs - if 'id' in self.attrs: - label_for = format_html(' for="{0}_{1}"', self.attrs['id'], self.index) + if self.id_for_label: + label_for = format_html(' for="{0}"', self.id_for_label) else: label_for = '' return format_html('{1} {2}', label_for, self.tag(), self.choice_label) @@ -619,13 +618,15 @@ class ChoiceInput(SubWidget): return self.value == self.choice_value def tag(self): - if 'id' in self.attrs: - self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index) final_attrs = dict(self.attrs, type=self.input_type, name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' return format_html('', flatatt(final_attrs)) + @property + def id_for_label(self): + return self.attrs.get('id', '') + class RadioChoiceInput(ChoiceInput): input_type = 'radio' diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 080d1fea86..951fbfa310 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -590,25 +590,26 @@ Selector and checkbox widgets .. code-block:: html
- +
- +
- +
- +
That included the ``