summaryrefslogtreecommitdiff
path: root/django/contrib/auth/forms.py
diff options
context:
space:
mode:
authorFabian Braun <fsbraun@gmx.de>2024-01-23 16:45:18 +0100
committerNatalia <124304+nessita@users.noreply.github.com>2024-02-20 12:13:32 -0300
commite626716c28b6286f8cf0f8174077f3d2244f3eb3 (patch)
tree7414e177d60175ed20fb0c4e7da5586ae154014b /django/contrib/auth/forms.py
parent8a757244f9e50c4d076e559e4b74b9d83ab089b6 (diff)
Fixed #34429 -- Allowed setting unusable passwords for users in the auth forms.
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
Diffstat (limited to 'django/contrib/auth/forms.py')
-rw-r--r--django/contrib/auth/forms.py79
1 files changed, 71 insertions, 8 deletions
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index db51f72f3b..5f71f03258 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -92,33 +92,78 @@ 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")):
password1 = forms.CharField(
label=label1,
+ required=False,
strip=False,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=label2,
+ required=False,
widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
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"
+ 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:
+ error = ValidationError(
+ self.fields[password1_field_name].error_messages["required"],
+ code="required",
+ )
+ self.add_error(password1_field_name, error)
+
+ if not password2:
+ error = ValidationError(
+ self.fields[password2_field_name].error_messages["required"],
+ code="required",
+ )
+ self.add_error(password2_field_name, error)
+
if password1 and password2 and password1 != password2:
error = ValidationError(
self.error_messages["password_mismatch"],
@@ -128,14 +173,17 @@ class SetPasswordMixin:
def validate_password_for_user(self, user, password_field_name="password2"):
password = self.cleaned_data.get(password_field_name)
- if password:
+ if password and self.cleaned_data["set_usable_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 self.cleaned_data["set_usable_password"]:
+ user.set_password(self.cleaned_data[password_field_name])
+ else:
+ user.set_unusable_password()
if commit:
user.save()
return user
@@ -148,6 +196,7 @@ class BaseUserCreationForm(SetPasswordMixin, forms.ModelForm):
"""
password1, password2 = SetPasswordMixin.create_password_fields()
+ usable_password = SetPasswordMixin.create_usable_password_field()
class Meta:
model = User
@@ -205,7 +254,7 @@ class UserChangeForm(forms.ModelForm):
label=_("Password"),
help_text=_(
"Raw passwords are not stored, so there is no way to see this "
- "user’s password, but you can change the password using "
+ "user’s password, but you can change or unset the password using "
'<a href="{}">this form</a>.'
),
)
@@ -219,6 +268,11 @@ class UserChangeForm(forms.ModelForm):
super().__init__(*args, **kwargs)
password = self.fields.get("password")
if password:
+ if self.instance and not self.instance.has_usable_password():
+ password.help_text = _(
+ "Enable password-based authentication for this user by setting a "
+ 'password using <a href="{}">this form</a>.'
+ )
password.help_text = password.help_text.format(
f"../../{self.instance.pk}/password/"
)
@@ -472,12 +526,22 @@ class AdminPasswordChangeForm(SetPasswordMixin, forms.Form):
"""
required_css_class = "required"
+ usable_password_help_text = SetPasswordMixin.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>"
+ )
password1, password2 = SetPasswordMixin.create_password_fields()
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
self.fields["password1"].widget.attrs["autofocus"] = True
+ if self.user.has_usable_password():
+ self.fields["usable_password"] = (
+ SetPasswordMixin.create_usable_password_field(
+ self.usable_password_help_text
+ )
+ )
def clean(self):
self.validate_passwords()
@@ -491,7 +555,6 @@ class AdminPasswordChangeForm(SetPasswordMixin, forms.Form):
@property
def changed_data(self):
data = super().changed_data
- for name in self.fields:
- if name not in data:
- return []
- return ["password"]
+ if "set_usable_password" in data or "password1" in data and "password2" in data:
+ return ["password"]
+ return []