diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 0fa30d70c7..d6b5702fff 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -284,7 +284,7 @@ class PasswordResetForm(forms.Form): 'email': email, 'domain': domain, 'site_name': site_name, - 'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(), + 'uid': urlsafe_base64_encode(force_bytes(user.pk)), 'user': user, 'token': token_generator.make_token(user), 'protocol': 'https' if use_https else 'http', diff --git a/django/utils/http.py b/django/utils/http.py index 5a063a9956..db18e57803 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -15,7 +15,6 @@ from urllib.parse import ( from django.core.exceptions import TooManyFieldsSent from django.utils.datastructures import MultiValueDict from django.utils.deprecation import RemovedInDjango30Warning -from django.utils.encoding import force_bytes from django.utils.functional import keep_lazy_text # based on RFC 7232, Appendix C @@ -220,10 +219,10 @@ def int_to_base36(i): def urlsafe_base64_encode(s): """ - Encode a bytestring in base64 for use in URLs. Strip any trailing equal - signs. + Encode a bytestring to a base64 string for use in URLs. Strip any trailing + equal signs. """ - return base64.urlsafe_b64encode(s).rstrip(b'\n=') + return base64.urlsafe_b64encode(s).rstrip(b'\n=').decode('ascii') def urlsafe_base64_decode(s): @@ -231,7 +230,7 @@ def urlsafe_base64_decode(s): Decode a base64 encoded string. Add back any trailing equal signs that might have been stripped. """ - s = force_bytes(s) + s = s.encode() try: return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, b'=')) except (LookupError, BinasciiError) as e: diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index f196a9b846..6f529d14fb 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -726,14 +726,22 @@ escaping HTML. .. function:: urlsafe_base64_encode(s) - Encodes a bytestring in base64 for use in URLs, stripping any trailing - equal signs. + Encodes a bytestring to a base64 string for use in URLs, stripping any + trailing equal signs. + + .. versionchanged:: 2.2 + + In older versions, it returns a bytestring instead of a string. .. function:: urlsafe_base64_decode(s) Decodes a base64 encoded string, adding back any trailing equal signs that might have been stripped. + .. versionchanged:: 2.2 + + In older versions, ``s`` may be a bytestring. + ``django.utils.module_loading`` =============================== diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index 7d85d30c4a..19c98639d8 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -298,6 +298,10 @@ Miscellaneous * Support for bytestring paths in the template filesystem loader is removed. +* :func:`django.utils.http.urlsafe_base64_encode` now returns a string instead + of a bytestring, and :func:`django.utils.http.urlsafe_base64_decode` may no + longer be passed a bytestring. + .. _deprecated-features-2.2: Features deprecated in 2.2 diff --git a/tests/auth_tests/test_templates.py b/tests/auth_tests/test_templates.py index 958ed47cd3..db35dc930d 100644 --- a/tests/auth_tests/test_templates.py +++ b/tests/auth_tests/test_templates.py @@ -47,7 +47,7 @@ class AuthTemplateTests(TestCase): client = PasswordResetConfirmClient() default_token_generator = PasswordResetTokenGenerator() token = default_token_generator.make_token(self.user) - uidb64 = urlsafe_base64_encode(str(self.user.pk).encode()).decode() + uidb64 = urlsafe_base64_encode(str(self.user.pk).encode()) url = reverse('password_reset_confirm', kwargs={'uidb64': uidb64, 'token': token}) response = client.get(url) self.assertContains(response, 'Enter new password') diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py index d12830ddc8..3d0c3ecadf 100644 --- a/tests/auth_tests/test_views.py +++ b/tests/auth_tests/test_views.py @@ -424,7 +424,7 @@ class UUIDUserPasswordResetTest(CustomUserPasswordResetTest): def test_confirm_invalid_uuid(self): """A uidb64 that decodes to a non-UUID doesn't crash.""" _, path = self._test_confirm_start() - invalid_uidb64 = urlsafe_base64_encode('INVALID_UUID'.encode()).decode() + invalid_uidb64 = urlsafe_base64_encode('INVALID_UUID'.encode()) first, _uuidb64_, second = path.strip('/').split('/') response = self.client.get('/' + '/'.join((first, invalid_uidb64, second)) + '/') self.assertContains(response, 'The password reset link was invalid')