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

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

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 

9 

10from weblate.accounts.models import AuditLog 

11 

12 

13class CharsPasswordValidator: 

14 """Validate whether the password is not only whitespace or single char.""" 

15 

16 def validate(self, password, user=None) -> None: 

17 if not password: 

18 return 

19 

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 ) 

30 

31 def get_help_text(self): 

32 return gettext( 

33 "Your password can't consist of a single character or only whitespace." 

34 ) 

35 

36 

37class MaximalLengthValidator: 

38 """Validate that the password is of a maximal length.""" 

39 

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 ) 

53 

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} 

60 

61 

62class PastPasswordsValidator: 

63 """Validate whether the password was not used before.""" 

64 

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) 

70 

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 ) 

77 

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 ) 

84 

85 def get_help_text(self): 

86 return gettext( 

87 "Your password can't match a password you have used in the past." 

88 )