Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
3db9a7a
[2.2.x] Set release date for 2.2.17.
carltongibson Nov 2, 2020
c769f65
[2.2.x] Bumped version for 2.2.17 release.
carltongibson Nov 2, 2020
3da29a3
[2.2.x] Post-release version bump.
carltongibson Nov 2, 2020
e893c0a
[2.2.x] Fixed #31850 -- Fixed BasicExtractorTests.test_extraction_war…
msmolens Oct 6, 2020
e8e28e7
[2.2.x] Updated CVE URL.
timgraham Jan 2, 2021
ee9d623
[2.2.x] Fixed GeoIPTest.test04_city() failure with the latest GeoIP2 …
felixxm Jan 29, 2021
21e7622
[2.2.x] Fixed CVE-2021-3281 -- Fixed potential directory-traversal vi…
felixxm Jan 22, 2021
fc0c8cf
[2.2.x] Bumped version for 2.2.18 release.
felixxm Feb 1, 2021
06ae7e0
[2.2.x] Post-release version bump.
felixxm Feb 1, 2021
34010d8
[2.2.x] Added CVE-2021-3281 to security archive.
felixxm Feb 1, 2021
226d831
[2.2.x] Added documentation extlink for bugs.python.org.
ngnpope Feb 16, 2021
fd6b6af
[2.2.x] Fixed CVE-2021-23336 -- Fixed web cache poisoning via django.…
ngnpope Feb 16, 2021
21a5547
[2.2.x] Bumped version for 2.2.19 release.
carltongibson Feb 19, 2021
1fb4628
[2.2.x] Post-release version bump.
carltongibson Feb 19, 2021
6e58828
[2.2.x] Added CVE-2021-23336 to security archive.
carltongibson Feb 19, 2021
4036d62
[2.2.x] Fixed CVE-2021-28658 -- Fixed potential directory-traversal v…
felixxm Mar 16, 2021
ad9fa56
[2.2.x] Bumped version for 2.2.20 release.
felixxm Apr 6, 2021
e95fbb6
[2.2.x] Post-release version bump.
felixxm Apr 6, 2021
7f1b088
[2.2.x] Added CVE-2021-28658 to security archive.
felixxm Apr 6, 2021
04ac162
[2.2.x] Fixed CVE-2021-31542 -- Tightened path & file name sanitation…
apollo13 Apr 14, 2021
ff1385a
[2.2.x] Bumped version for 2.2.21 release.
carltongibson May 4, 2021
3931dc7
[2.2.x] Post-release version bump.
carltongibson May 4, 2021
bcafd9b
[2.2.x] Added CVE-2021-31542 to security archive.
carltongibson May 4, 2021
1637003
[2.2.x] Refs CVE-2021-31542 -- Skipped mock AWS storage test on Windows.
carltongibson May 4, 2021
d9594c4
[2.2.x] Fixed #32713, Fixed CVE-2021-32052 -- Prevented newlines and …
felixxm May 4, 2021
df9fd46
[2.2.x] Bumped version for 2.2.22 release.
felixxm May 6, 2021
7ada1f9
[2.2.x] Post-release version bump.
felixxm May 6, 2021
88d9b28
[2.2.x] Added CVE-2021-32052 to security archive.
felixxm May 6, 2021
3ba089a
[2.2.x] Refs #32718 -- Corrected CVE-2021-31542 release notes.
felixxm May 12, 2021
b8ecb06
[2.2.x] Fixed #32718 -- Relaxed file name validation in FileField.
felixxm May 13, 2021
61f814f
[2.2.x] Bumped version for 2.2.23 release.
felixxm May 13, 2021
5fe4970
[2.2.x] Post-release version bump.
felixxm May 13, 2021
63f0d7a
[2.2.x] Refs #32718 -- Fixed file_storage.test_generate_filename and …
felixxm May 14, 2021
bed1755
[2.2.x] Changed IRC references to Libera.Chat.
felixxm May 20, 2021
f163ad5
[2.2.x] Added stub release notes and date for Django 2.2.24.
carltongibson May 25, 2021
6229d87
[2.2.x] Confirmed release date for Django 2.2.24.
carltongibson Jun 2, 2021
053cc95
[2.2.x] Fixed CVE-2021-33203 -- Fixed potential path-traversal via ad…
apollo13 May 25, 2021
f27c38a
[2.2.x] Fixed CVE-2021-33571 -- Prevented leading zeros in IPv4 addre…
felixxm May 25, 2021
2da029d
[2.2.x] Bumped version for 2.2.24 release.
carltongibson Jun 2, 2021
48bde7c
[2.2.x] Post-release version bump.
carltongibson Jun 2, 2021
3e7bb56
[2.2.x] Added CVE-2021-33203 and CVE-2021-33571 to security archive.
carltongibson Jun 2, 2021
dc43667
[2.2.x] Fixed docs header underlines in security archive.
felixxm Jun 2, 2021
837ffcf
[2.2.x] Refs #32856 -- Doc'd that psycopg2 < 2.9 is required.
felixxm Jun 21, 2021
8f59f72
[2.2.x] Refs #31676 -- Removed Django Core-Mentorship mailing list re…
felixxm Jul 13, 2021
d4d1c2b
[2.2.x] Refs #31676 -- Removed Core team from organization docs.
felixxm Jul 20, 2021
66008c2
[2.2.x] Refs #31676 -- Added Mergers and Releasers to organization docs.
felixxm Jul 20, 2021
a9c0aa1
[2.2.x] Refs #31676 -- Updated technical board description in organiz…
felixxm Jul 21, 2021
05bc1c8
[2.2.x] Fixed #33082 -- Fixed CommandTests.test_subparser_invalid_opt…
felixxm Sep 2, 2021
cf63dd5
[2.2.x] Added 'formatter' to spelling wordlist.
felixxm Oct 12, 2021
12141e3
[2.2.x] Refs #32856 -- Clarified that psycopg2 < 2.9 is required.
felixxm Nov 3, 2021
029c830
[2.2.x] Fixed #33247 -- Added configuration for Read The Docs.
carltongibson Nov 3, 2021
9a4a2b2
[2.2.x] Refs #33247 -- Corrected configuration for Read The Docs.
carltongibson Nov 3, 2021
5289fcf
[2.2.x] Configured Read The Docs to build all formats.
adamchainz Nov 18, 2021
4bc10b7
[2.2.x] Fixed crash building HTML docs since Sphinx 4.3.
felixxm Nov 17, 2021
fac0fdd
[2.2.x] Added stub release notes for 2.2.25.
felixxm Nov 29, 2021
0007a5f
[2.2.x] Added requirements.txt to files ignored by Sphinx builds.
felixxm Nov 30, 2021
7cf7d74
[2.2.x] Fixed #30530, CVE-2021-44420 -- Fixed potential bypass of an …
apollo13 Nov 29, 2021
79d8dce
[2.2.x] Bumped version for 2.2.25 release.
felixxm Dec 7, 2021
8439938
[2.2.x] Post-release version bump.
felixxm Dec 7, 2021
573e70e
[2.2.x] Added CVE-2021-44420 to security archive.
felixxm Dec 7, 2021
b878206
[2.2.x] Refs #33365, Refs #30530 -- Doc'd re_path() behavior change i…
felixxm Dec 15, 2021
03b733d
[2.2.x] Added stub release notes for 2.2.26 release.
carltongibson Dec 27, 2021
2135637
[2.2.x] Fixed CVE-2021-45115 -- Prevented DoS vector in UserAttribute…
apollo13 Dec 27, 2021
c9f648c
[2.2.x] Fixed CVE-2021-45116 -- Fixed potential information disclosur…
apollo13 Dec 27, 2021
4cb35b3
[2.2.x] Fixed CVE-2021-45452 -- Fixed potential path traversal in sto…
apollo13 Dec 17, 2021
44e7cca
2.2.x] Bumped version for 2.2.26 release.
carltongibson Jan 4, 2022
e085d46
[2.2.x] Post-release version bump.
carltongibson Jan 4, 2022
77d0fe5
[2.2.x] Added CVE-2021-45115, CVE-2021-45116, and CVE-2021-45452 to s…
carltongibson Jan 4, 2022
4cafd3a
[2.2.x] Added stub release notes 2.2.27.
felixxm Jan 11, 2022
c27a7eb
[2.2.x] Fixed CVE-2022-22818 -- Fixed possible XSS via {% debug %} te…
MarkusH Jan 1, 2022
c477b76
[2.2.x] Fixed CVE-2022-23833 -- Fixed DoS possiblity in file uploads.
felixxm Jan 21, 2022
e541f2d
[2.2.x] Bumped version for 2.2.27 release.
felixxm Feb 1, 2022
2427b2f
[2.2.x] Post-release version bump.
felixxm Feb 1, 2022
047ece3
[2.2.x] Added CVE-2022-22818 and CVE-2022-23833 to security archive.
felixxm Feb 1, 2022
9d13d8c
[2.2.x] Fixed typo in release notes.
smithdc1 Feb 2, 2022
e03648f
[2.2.x] Fixed forms_tests.tests.test_renderers with Jinja 3.1.0+.
felixxm Mar 25, 2022
2801f29
[2.2.x] Reverted "Fixed forms_tests.tests.test_renderers with Jinja 3…
felixxm Mar 26, 2022
8352b98
[2.2.x] Added stub release notes for 2.2.28.
felixxm Apr 1, 2022
2c09e68
[2.2.x] Fixed CVE-2022-28346 -- Protected QuerySet.annotate(), aggreg…
felixxm Apr 1, 2022
29a6c98
[2.2.x] Fixed CVE-2022-28347 -- Protected QuerySet.explain(**options)…
felixxm Apr 1, 2022
5c33000
[2.2.x] Bumped version for 2.2.28 release.
felixxm Apr 11, 2022
d3731c9
[2.2.x] Post-release version bump.
felixxm Apr 11, 2022
2a62cdc
[2.2.x] Added CVE-2022-28346 and CVE-2022-28347 to security archive.
felixxm Apr 11, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Configuration for the Read The Docs (RTD) builds of the documentation.
# Ref: https://docs.readthedocs.io/en/stable/config-file/v2.html
# The python.install.requirements pins the version of Sphinx used.
version: 2

build:
os: ubuntu-20.04
tools:
python: "3.8"

sphinx:
configuration: docs/conf.py

python:
install:
- requirements: docs/requirements.txt

formats: all
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,7 @@ answer newbie questions, and generally made Django that much better:
mattycakes@gmail.com
Max Burstein <http://maxburstein.com>
Max Derkachev <mderk@yandex.ru>
Max Smolens <msmolens@gmail.com>
Maxime Lorant <maxime.lorant@gmail.com>
Maxime Turcotte <maxocub@riseup.net>
Maximilian Merz <django@mxmerz.de>
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ticket here: https://code.djangoproject.com/newticket

To get more help:

* Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang
* Join the ``#django`` channel on ``irc.libera.chat``. Lots of helpful people
out there. See https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're
new to IRC.

Expand Down
2 changes: 1 addition & 1 deletion django/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.utils.version import get_version

VERSION = (2, 2, 17, 'alpha', 0)
VERSION = (2, 2, 29, 'alpha', 0)

__version__ = get_version(VERSION)

Expand Down
3 changes: 2 additions & 1 deletion django/contrib/admindocs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from django.http import Http404
from django.template.engine import Engine
from django.urls import get_mod_func, get_resolver, get_urlconf
from django.utils._os import safe_join
from django.utils.decorators import method_decorator
from django.utils.inspect import (
func_accepts_kwargs, func_accepts_var_args, get_func_full_args,
Expand Down Expand Up @@ -328,7 +329,7 @@ def get_context_data(self, **kwargs):
else:
# This doesn't account for template loaders (#24128).
for index, directory in enumerate(default_engine.dirs):
template_file = Path(directory) / template
template_file = Path(safe_join(directory, template))
if template_file.exists():
with template_file.open() as f:
template_contents = f.read()
Expand Down
40 changes: 38 additions & 2 deletions django/contrib/auth/password_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,36 @@ def get_help_text(self):
) % {'min_length': self.min_length}


def exceeds_maximum_length_ratio(password, max_similarity, value):
"""
Test that value is within a reasonable range of password.
The following ratio calculations are based on testing SequenceMatcher like
this:
for i in range(0,6):
print(10**i, SequenceMatcher(a='A', b='A'*(10**i)).quick_ratio())
which yields:
1 1.0
10 0.18181818181818182
100 0.019801980198019802
1000 0.001998001998001998
10000 0.00019998000199980003
100000 1.999980000199998e-05
This means a length_ratio of 10 should never yield a similarity higher than
0.2, for 100 this is down to 0.02 and for 1000 it is 0.002. This can be
calculated via 2 / length_ratio. As a result we avoid the potentially
expensive sequence matching.
"""
pwd_len = len(password)
length_bound_similarity = max_similarity / 2 * pwd_len
value_len = len(value)
return pwd_len >= 10 * value_len and value_len < length_bound_similarity


class UserAttributeSimilarityValidator:
"""
Validate whether the password is sufficiently different from the user's
Expand All @@ -130,19 +160,25 @@ class UserAttributeSimilarityValidator:

def __init__(self, user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7):
self.user_attributes = user_attributes
if max_similarity < 0.1:
raise ValueError('max_similarity must be at least 0.1')
self.max_similarity = max_similarity

def validate(self, password, user=None):
if not user:
return

password = password.lower()
for attribute_name in self.user_attributes:
value = getattr(user, attribute_name, None)
if not value or not isinstance(value, str):
continue
value_parts = re.split(r'\W+', value) + [value]
value_lower = value.lower()
value_parts = re.split(r'\W+', value_lower) + [value_lower]
for value_part in value_parts:
if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() >= self.max_similarity:
if exceeds_maximum_length_ratio(password, self.max_similarity, value_part):
continue
if SequenceMatcher(a=password, b=value_part).quick_ratio() >= self.max_similarity:
try:
verbose_name = str(user._meta.get_field(attribute_name).verbose_name)
except FieldDoesNotExist:
Expand Down
16 changes: 15 additions & 1 deletion django/core/files/storage.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import os
import pathlib
from datetime import datetime
from urllib.parse import urljoin

from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from django.core.files import File, locks
from django.core.files.move import file_move_safe
from django.core.files.utils import validate_file_name
from django.core.signals import setting_changed
from django.utils import timezone
from django.utils._os import safe_join
Expand Down Expand Up @@ -49,7 +51,10 @@ def save(self, name, content, max_length=None):
content = File(content, name)

name = self.get_available_name(name, max_length=max_length)
return self._save(name, content)
name = self._save(name, content)
# Ensure that the name returned from the storage system is still valid.
validate_file_name(name, allow_relative_path=True)
return name

# These methods are part of the public API, with default implementations.

Expand All @@ -65,7 +70,11 @@ def get_available_name(self, name, max_length=None):
Return a filename that's free on the target storage system and
available for new content to be written to.
"""
name = str(name).replace('\\', '/')
dir_name, file_name = os.path.split(name)
if '..' in pathlib.PurePath(dir_name).parts:
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
validate_file_name(file_name)
file_root, file_ext = os.path.splitext(file_name)
# If the filename already exists, add an underscore and a random 7
# character alphanumeric string (before the file extension, if one
Expand Down Expand Up @@ -96,8 +105,11 @@ def generate_filename(self, filename):
Validate the filename by calling get_valid_name() and return a filename
to be passed to the save() method.
"""
filename = str(filename).replace('\\', '/')
# `filename` may include a path as returned by FileField.upload_to.
dirname, filename = os.path.split(filename)
if '..' in pathlib.PurePath(dirname).parts:
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dirname)
return os.path.normpath(os.path.join(dirname, self.get_valid_name(filename)))

def path(self, name):
Expand Down Expand Up @@ -289,6 +301,8 @@ def _save(self, name, content):
if self.file_permissions_mode is not None:
os.chmod(full_path, self.file_permissions_mode)

# Ensure the saved path is always relative to the storage root.
name = os.path.relpath(full_path, self.location)
# Store filenames with forward slashes, even on Windows.
return name.replace('\\', '/')

Expand Down
3 changes: 3 additions & 0 deletions django/core/files/uploadedfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.conf import settings
from django.core.files import temp as tempfile
from django.core.files.base import File
from django.core.files.utils import validate_file_name

__all__ = ('UploadedFile', 'TemporaryUploadedFile', 'InMemoryUploadedFile',
'SimpleUploadedFile')
Expand Down Expand Up @@ -47,6 +48,8 @@ def _set_name(self, name):
ext = ext[:255]
name = name[:255 - len(ext)] + ext

name = validate_file_name(name)

self._name = name

name = property(_get_name, _set_name)
Expand Down
26 changes: 26 additions & 0 deletions django/core/files/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
import os
import pathlib

from django.core.exceptions import SuspiciousFileOperation


def validate_file_name(name, allow_relative_path=False):
# Remove potentially dangerous names
if os.path.basename(name) in {'', '.', '..'}:
raise SuspiciousFileOperation("Could not derive file name from '%s'" % name)

if allow_relative_path:
# Use PurePosixPath() because this branch is checked only in
# FileField.generate_filename() where all file paths are expected to be
# Unix style (with forward slashes).
path = pathlib.PurePosixPath(name)
if path.is_absolute() or '..' in path.parts:
raise SuspiciousFileOperation(
"Detected path traversal attempt in '%s'" % name
)
elif name != os.path.basename(name):
raise SuspiciousFileOperation("File name '%s' includes path elements" % name)

return name


class FileProxyMixin:
"""
A mixin class used to forward file methods to an underlaying file
Expand Down
19 changes: 17 additions & 2 deletions django/core/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class URLValidator(RegexValidator):
ul = '\u00a1-\uffff' # unicode letters range (must not be a raw string)

# IP patterns
ipv4_re = r'(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
ipv4_re = r'(?:0|25[0-5]|2[0-4]\d|1\d?\d?|[1-9]\d?)(?:\.(?:0|25[0-5]|2[0-4]\d|1\d?\d?|[1-9]\d?)){3}'
ipv6_re = r'\[[0-9a-f:\.]+\]' # (simple regex, validated later)

# Host patterns
Expand All @@ -101,14 +101,17 @@ class URLValidator(RegexValidator):
r'\Z', re.IGNORECASE)
message = _('Enter a valid URL.')
schemes = ['http', 'https', 'ftp', 'ftps']
unsafe_chars = frozenset('\t\r\n')

def __init__(self, schemes=None, **kwargs):
super().__init__(**kwargs)
if schemes is not None:
self.schemes = schemes

def __call__(self, value):
# Check first if the scheme is valid
if isinstance(value, str) and self.unsafe_chars.intersection(value):
raise ValidationError(self.message, code=self.code)
# Check if the scheme is valid.
scheme = value.split('://')[0].lower()
if scheme not in self.schemes:
raise ValidationError(self.message, code=self.code)
Expand Down Expand Up @@ -253,6 +256,18 @@ def validate_ipv4_address(value):
ipaddress.IPv4Address(value)
except ValueError:
raise ValidationError(_('Enter a valid IPv4 address.'), code='invalid')
else:
# Leading zeros are forbidden to avoid ambiguity with the octal
# notation. This restriction is included in Python 3.9.5+.
# TODO: Remove when dropping support for PY39.
if any(
octet != '0' and octet[0] == '0'
for octet in value.split('.')
):
raise ValidationError(
_('Enter a valid IPv4 address.'),
code='invalid',
)


def validate_ipv6_address(value):
Expand Down
1 change: 0 additions & 1 deletion django/db/backends/postgresql/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_over_clause = True
supports_aggregate_filter_clause = True
supported_explain_formats = {'JSON', 'TEXT', 'XML', 'YAML'}
validates_explain_options = False # A query will error on invalid options.

@cached_property
def is_postgresql_9_5(self):
Expand Down
27 changes: 22 additions & 5 deletions django/db/backends/postgresql/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
class DatabaseOperations(BaseDatabaseOperations):
cast_char_field_without_max_length = 'varchar'
explain_prefix = 'EXPLAIN'
explain_options = frozenset(
[
"ANALYZE",
"BUFFERS",
"COSTS",
"SETTINGS",
"SUMMARY",
"TIMING",
"VERBOSE",
"WAL",
]
)
cast_data_types = {
'AutoField': 'integer',
'BigAutoField': 'bigint',
Expand Down Expand Up @@ -267,15 +279,20 @@ def window_frame_range_start_end(self, start=None, end=None):
return start_, end_

def explain_query_prefix(self, format=None, **options):
prefix = super().explain_query_prefix(format)
extra = {}
if format:
extra['FORMAT'] = format
# Normalize options.
if options:
extra.update({
options = {
name.upper(): 'true' if value else 'false'
for name, value in options.items()
})
}
for valid_option in self.explain_options:
value = options.pop(valid_option, None)
if value is not None:
extra[valid_option.upper()] = value
prefix = super().explain_query_prefix(format, **options)
if format:
extra['FORMAT'] = format
if extra:
prefix += ' (%s)' % ', '.join('%s %s' % i for i in extra.items())
return prefix
Expand Down
2 changes: 2 additions & 0 deletions django/db/models/fields/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.core.files.base import File
from django.core.files.images import ImageFile
from django.core.files.storage import default_storage
from django.core.files.utils import validate_file_name
from django.db.models import signals
from django.db.models.fields import Field
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -304,6 +305,7 @@ def generate_filename(self, instance, filename):
else:
dirname = datetime.datetime.now().strftime(self.upload_to)
filename = posixpath.join(dirname, filename)
filename = validate_file_name(filename, allow_relative_path=True)
return self.storage.generate_filename(filename)

def save_form_data(self, instance, data):
Expand Down
Loading