Coverage for app/venv/lib/python3.14/site-packages/weblate/accounts/password_validation.py: 27%
34 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from django.conf import settings
6from django.contrib.auth.hashers import check_password
7from django.core.exceptions import ValidationError
8from django.utils.translation import gettext, ngettext
10from weblate.accounts.models import AuditLog
13class CharsPasswordValidator:
14 """Validate whether the password is not only whitespace or single char."""
16 def validate(self, password, user=None) -> None:
17 if not password:
18 return
20 if not password.strip():
21 raise ValidationError(
22 gettext("This password consists of only whitespace."),
23 code="password_whitespace",
24 )
25 if not password.strip(password[0]):
26 raise ValidationError(
27 gettext("This password is only a single character."),
28 code="password_same_chars",
29 )
31 def get_help_text(self):
32 return gettext(
33 "Your password can't consist of a single character or only whitespace."
34 )
37class MaximalLengthValidator:
38 """Validate that the password is of a maximal length."""
40 def validate(self, password, user=None) -> None:
41 if len(password) > settings.MAXIMAL_PASSWORD_LENGTH:
42 raise ValidationError(
43 ngettext(
44 "This password is too long. It must contain at most "
45 "%(max_length)d character.",
46 "This password is too long. It must contain at most "
47 "%(max_length)d characters.",
48 settings.MAXIMAL_PASSWORD_LENGTH,
49 ),
50 code="password_too_long",
51 params={"max_length": settings.MAXIMAL_PASSWORD_LENGTH},
52 )
54 def get_help_text(self):
55 return ngettext(
56 "Your password must contain at most %(max_length)d character.",
57 "Your password must contain at most %(max_length)d characters.",
58 settings.MAXIMAL_PASSWORD_LENGTH,
59 ) % {"max_length": settings.MAXIMAL_PASSWORD_LENGTH}
62class PastPasswordsValidator:
63 """Validate whether the password was not used before."""
65 def validate(self, password, user=None) -> None:
66 if user is not None:
67 passwords = []
68 if user.has_usable_password():
69 passwords.append(user.password)
71 if user.pk:
72 passwords.extend(
73 log.params["password"]
74 for log in AuditLog.objects.get_past_passwords(user=user)
75 if "password" in log.params
76 )
78 for old in passwords:
79 if check_password(password, old):
80 raise ValidationError(
81 gettext("Can not reuse previously used password."),
82 code="password-past",
83 )
85 def get_help_text(self):
86 return gettext(
87 "Your password can't match a password you have used in the past."
88 )