summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorNatalia <124304+nessita@users.noreply.github.com>2024-08-15 10:27:24 -0300
committerNatalia <124304+nessita@users.noreply.github.com>2024-08-19 12:41:23 -0300
commitda22e6cb3cdb51f2f7ca45e1591d2c6a8be458e0 (patch)
treec77f623801b3afe99b27711cef972b38e52b45b9 /django
parentcfad0655c235ec532b40841d634b0f97e6280a4e (diff)
[5.1.x] Fixed #35678 -- Removed "usable_password" field from BaseUserCreationForm.
Refs #34429: Following the implementation allowing the setting of unusable passwords via the admin site, the `BaseUserCreationForm` and `UserCreationForm` were extended to include a new field for choosing whether password-based authentication for the new user should be enabled or disabled at creation time. Given that these forms are designed to be extended when implementing custom user models, this branch ensures that this new field is moved to a new, admin-dedicated, user creation form `AdminUserCreationForm`. Regression in e626716c28b6286f8cf0f8174077f3d2244f3eb3. Thanks Simon Willison for the report, Fabian Braun and Sarah Boyce for the review. Backport of 0ebed5fa95f53b87383901bbd9341ef3c974344f from main.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/auth/admin.py4
-rw-r--r--django/contrib/auth/forms.py98
2 files changed, 66 insertions, 36 deletions
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 90a53a142c..00d7e79811 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -5,8 +5,8 @@ from django.contrib.admin.utils import unquote
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import (
AdminPasswordChangeForm,
+ AdminUserCreationForm,
UserChangeForm,
- UserCreationForm,
)
from django.contrib.auth.models import Group, User
from django.core.exceptions import PermissionDenied
@@ -71,7 +71,7 @@ class UserAdmin(admin.ModelAdmin):
),
)
form = UserChangeForm
- add_form = UserCreationForm
+ add_form = AdminUserCreationForm
change_password_form = AdminPasswordChangeForm
list_display = ("username", "email", "first_name", "last_name", "is_staff")
list_filter = ("is_staff", "is_superuser", "is_active", "groups")
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 31e96ff91c..a11668944a 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -96,18 +96,11 @@ class UsernameField(forms.CharField):
class SetPasswordMixin:
"""
Form mixin that validates and sets a password for a user.
-
- This mixin also support setting an unusable password for a user.
"""
error_messages = {
"password_mismatch": _("The two password fields didn’t match."),
}
- usable_password_help_text = _(
- "Whether the user will be able to authenticate using a password or not. "
- "If disabled, they may still be able to authenticate using other backends, "
- "such as Single Sign-On or LDAP."
- )
@staticmethod
def create_password_fields(label1=_("Password"), label2=_("Password confirmation")):
@@ -127,33 +120,14 @@ class SetPasswordMixin:
)
return password1, password2
- @staticmethod
- def create_usable_password_field(help_text=usable_password_help_text):
- return forms.ChoiceField(
- label=_("Password-based authentication"),
- required=False,
- initial="true",
- choices={"true": _("Enabled"), "false": _("Disabled")},
- widget=forms.RadioSelect(attrs={"class": "radiolist inline"}),
- help_text=help_text,
- )
-
def validate_passwords(
self,
password1_field_name="password1",
password2_field_name="password2",
- usable_password_field_name="usable_password",
):
- usable_password = (
- self.cleaned_data.pop(usable_password_field_name, None) != "false"
- )
- self.cleaned_data["set_usable_password"] = usable_password
password1 = self.cleaned_data.get(password1_field_name)
password2 = self.cleaned_data.get(password2_field_name)
- if not usable_password:
- return self.cleaned_data
-
if not password1 and password1_field_name not in self.errors:
error = ValidationError(
self.fields[password1_field_name].error_messages["required"],
@@ -177,19 +151,68 @@ class SetPasswordMixin:
def validate_password_for_user(self, user, password_field_name="password2"):
password = self.cleaned_data.get(password_field_name)
- if password and self.cleaned_data["set_usable_password"]:
+ if password:
try:
password_validation.validate_password(password, user)
except ValidationError as error:
self.add_error(password_field_name, error)
def set_password_and_save(self, user, password_field_name="password1", commit=True):
+ user.set_password(self.cleaned_data[password_field_name])
+ if commit:
+ user.save()
+ return user
+
+
+class SetUnusablePasswordMixin:
+ """
+ Form mixin that allows setting an unusable password for a user.
+
+ This mixin should be used in combination with `SetPasswordMixin`.
+ """
+
+ usable_password_help_text = _(
+ "Whether the user will be able to authenticate using a password or not. "
+ "If disabled, they may still be able to authenticate using other backends, "
+ "such as Single Sign-On or LDAP."
+ )
+
+ @staticmethod
+ def create_usable_password_field(help_text=usable_password_help_text):
+ return forms.ChoiceField(
+ label=_("Password-based authentication"),
+ required=False,
+ initial="true",
+ choices={"true": _("Enabled"), "false": _("Disabled")},
+ widget=forms.RadioSelect(attrs={"class": "radiolist inline"}),
+ help_text=help_text,
+ )
+
+ def validate_passwords(
+ self,
+ *args,
+ usable_password_field_name="usable_password",
+ **kwargs,
+ ):
+ usable_password = (
+ self.cleaned_data.pop(usable_password_field_name, None) != "false"
+ )
+ self.cleaned_data["set_usable_password"] = usable_password
+
+ if usable_password:
+ super().validate_passwords(*args, **kwargs)
+
+ def validate_password_for_user(self, user, **kwargs):
if self.cleaned_data["set_usable_password"]:
- user.set_password(self.cleaned_data[password_field_name])
+ super().validate_password_for_user(user, **kwargs)
+
+ def set_password_and_save(self, user, commit=True, **kwargs):
+ if self.cleaned_data["set_usable_password"]:
+ user = super().set_password_and_save(user, **kwargs, commit=commit)
else:
user.set_unusable_password()
- if commit:
- user.save()
+ if commit:
+ user.save()
return user
@@ -197,10 +220,12 @@ class BaseUserCreationForm(SetPasswordMixin, forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and
password.
+
+ This is the documented base class for customizing the user creation form.
+ It should be kept mostly unchanged to ensure consistency and compatibility.
"""
password1, password2 = SetPasswordMixin.create_password_fields()
- usable_password = SetPasswordMixin.create_usable_password_field()
class Meta:
model = User
@@ -520,13 +545,13 @@ class PasswordChangeForm(SetPasswordForm):
return old_password
-class AdminPasswordChangeForm(SetPasswordMixin, forms.Form):
+class AdminPasswordChangeForm(SetUnusablePasswordMixin, SetPasswordMixin, forms.Form):
"""
A form used to change the password of a user in the admin interface.
"""
required_css_class = "required"
- usable_password_help_text = SetPasswordMixin.usable_password_help_text + (
+ usable_password_help_text = SetUnusablePasswordMixin.usable_password_help_text + (
'<ul id="id_unusable_warning" class="messagelist"><li class="warning">'
"If disabled, the current password for this user will be lost.</li></ul>"
)
@@ -538,7 +563,7 @@ class AdminPasswordChangeForm(SetPasswordMixin, forms.Form):
self.fields["password1"].widget.attrs["autofocus"] = True
if self.user.has_usable_password():
self.fields["usable_password"] = (
- SetPasswordMixin.create_usable_password_field(
+ SetUnusablePasswordMixin.create_usable_password_field(
self.usable_password_help_text
)
)
@@ -558,3 +583,8 @@ class AdminPasswordChangeForm(SetPasswordMixin, forms.Form):
if "set_usable_password" in data or "password1" in data and "password2" in data:
return ["password"]
return []
+
+
+class AdminUserCreationForm(SetUnusablePasswordMixin, UserCreationForm):
+
+ usable_password = SetUnusablePasswordMixin.create_usable_password_field()