From 1ce7dbf18e4dd4bb59d373cc8de35c329acecf0e Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 6 Oct 2026 13:58:55 +0300 Subject: [PATCH] fix(computes): editing a compute keeps its password unless a new one is given The TCP and TLS compute forms never show the stored password, so the field is empty on every edit. Saving the form without typing the password again replaced it with nothing, and the next connection to the host failed to authenticate. An empty password field on an edit now keeps the stored password; a new value replaces it. --- computes/forms.py | 12 ++++++++++-- computes/test_compute_password.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 computes/test_compute_password.py diff --git a/computes/forms.py b/computes/forms.py index 11286957..dcf39daa 100644 --- a/computes/forms.py +++ b/computes/forms.py @@ -7,7 +7,15 @@ from computes.models import Compute from .validators import validate_hostname -class TcpComputeForm(forms.ModelForm): +class KeepPasswordMixin: + """The form never shows the stored password: left empty on an edit, it + stays as it is.""" + + def clean_password(self): + return self.cleaned_data["password"] or self.instance.password + + +class TcpComputeForm(KeepPasswordMixin, forms.ModelForm): hostname = forms.CharField(validators=[validate_hostname]) type = forms.IntegerField(widget=forms.HiddenInput, initial=CONN_TCP) @@ -26,7 +34,7 @@ class SshComputeForm(forms.ModelForm): exclude = ["password"] -class TlsComputeForm(forms.ModelForm): +class TlsComputeForm(KeepPasswordMixin, forms.ModelForm): hostname = forms.CharField(validators=[validate_hostname]) type = forms.IntegerField(widget=forms.HiddenInput, initial=CONN_TLS) diff --git a/computes/test_compute_password.py b/computes/test_compute_password.py new file mode 100644 index 00000000..c0e7e5d6 --- /dev/null +++ b/computes/test_compute_password.py @@ -0,0 +1,28 @@ +"""The compute form never shows the stored password: a password left empty +on an edit keeps it, a new one replaces it.""" + +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse + +from computes.models import Compute + + +class ComputePasswordTests(TestCase): + def setUp(self): + self.client.force_login(get_user_model().objects.create_superuser("cp-admin", "cp@example.com", "pw")) + + def edit(self, compute, password): + data = {"name": compute.name, "hostname": compute.hostname, "login": "admin", "password": password, + "type": compute.type, "details": ""} + self.client.post(reverse("compute_update", args=[compute.id]), data) + compute.refresh_from_db() + return compute.password + + def test_an_empty_password_keeps_the_stored_one(self): + for conn_type in (1, 3): # TCP, TLS + with self.subTest(type=conn_type): + compute = Compute.objects.create(name=f"cp{conn_type}", hostname="10.0.0.7", login="admin", + password="secret", type=conn_type) + self.assertEqual(self.edit(compute, ""), "secret") + self.assertEqual(self.edit(compute, "other"), "other")