diff options
| author | Fabian Braun <fsbraun@gmx.de> | 2024-01-23 16:45:18 +0100 |
|---|---|---|
| committer | Natalia <124304+nessita@users.noreply.github.com> | 2024-02-20 12:13:32 -0300 |
| commit | e626716c28b6286f8cf0f8174077f3d2244f3eb3 (patch) | |
| tree | 7414e177d60175ed20fb0c4e7da5586ae154014b /django/contrib/auth/forms.py | |
| parent | 8a757244f9e50c4d076e559e4b74b9d83ab089b6 (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.py | 79 |
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 [] |
