* Make password validation less intrusive * Update Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
This commit is contained in:
parent
32442ac7f3
commit
f2492ca574
|
|
@ -0,0 +1,85 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordStrength(Enum):
|
||||||
|
VERY_WEAK = 'very weak'
|
||||||
|
WEAK = 'weak'
|
||||||
|
MODERATE = 'moderate'
|
||||||
|
STRONG = 'strong'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
match self:
|
||||||
|
case PasswordStrength.VERY_WEAK: return str(_('very weak'))
|
||||||
|
case PasswordStrength.WEAK: return str(_('weak'))
|
||||||
|
case PasswordStrength.MODERATE: return str(_('moderate'))
|
||||||
|
case PasswordStrength.STRONG: return str(_('strong'))
|
||||||
|
|
||||||
|
def color(self):
|
||||||
|
match self:
|
||||||
|
case PasswordStrength.VERY_WEAK: return 'red'
|
||||||
|
case PasswordStrength.WEAK: return 'red'
|
||||||
|
case PasswordStrength.MODERATE: return 'yellow'
|
||||||
|
case PasswordStrength.STRONG: return 'green'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def strength(cls, password: str) -> 'PasswordStrength':
|
||||||
|
digit = any(character.isdigit() for character in password)
|
||||||
|
upper = any(character.isupper() for character in password)
|
||||||
|
lower = any(character.islower() for character in password)
|
||||||
|
symbol = any(not character.isalnum() for character in password)
|
||||||
|
return cls._check_password_strength(digit, upper, lower, symbol, len(password))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _check_password_strength(
|
||||||
|
cls,
|
||||||
|
digit: bool,
|
||||||
|
upper: bool,
|
||||||
|
lower: bool,
|
||||||
|
symbol: bool,
|
||||||
|
length: int
|
||||||
|
) -> 'PasswordStrength':
|
||||||
|
# suggested evaluation
|
||||||
|
# https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163
|
||||||
|
if digit and upper and lower and symbol:
|
||||||
|
match length:
|
||||||
|
case num if 13 <= num:
|
||||||
|
return PasswordStrength.STRONG
|
||||||
|
case num if 11 <= num <= 12:
|
||||||
|
return PasswordStrength.MODERATE
|
||||||
|
case num if 7 <= num <= 10:
|
||||||
|
return PasswordStrength.WEAK
|
||||||
|
case num if num <= 6:
|
||||||
|
return PasswordStrength.VERY_WEAK
|
||||||
|
elif digit and upper and lower:
|
||||||
|
match length:
|
||||||
|
case num if 14 <= num:
|
||||||
|
return PasswordStrength.STRONG
|
||||||
|
case num if 11 <= num <= 13:
|
||||||
|
return PasswordStrength.MODERATE
|
||||||
|
case num if 7 <= num <= 10:
|
||||||
|
return PasswordStrength.WEAK
|
||||||
|
case num if num <= 6:
|
||||||
|
return PasswordStrength.VERY_WEAK
|
||||||
|
elif upper and lower:
|
||||||
|
match length:
|
||||||
|
case num if 15 <= num:
|
||||||
|
return PasswordStrength.STRONG
|
||||||
|
case num if 12 <= num <= 14:
|
||||||
|
return PasswordStrength.MODERATE
|
||||||
|
case num if 7 <= num <= 11:
|
||||||
|
return PasswordStrength.WEAK
|
||||||
|
case num if num <= 6:
|
||||||
|
return PasswordStrength.VERY_WEAK
|
||||||
|
elif lower or upper:
|
||||||
|
match length:
|
||||||
|
case num if 18 <= num:
|
||||||
|
return PasswordStrength.STRONG
|
||||||
|
case num if 14 <= num <= 17:
|
||||||
|
return PasswordStrength.MODERATE
|
||||||
|
case num if 9 <= num <= 13:
|
||||||
|
return PasswordStrength.WEAK
|
||||||
|
case num if num <= 8:
|
||||||
|
return PasswordStrength.VERY_WEAK
|
||||||
|
|
||||||
|
return PasswordStrength.VERY_WEAK
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict, List, Union, Any, TYPE_CHECKING
|
from typing import Dict, List, Union, Any, TYPE_CHECKING
|
||||||
|
|
||||||
|
from .password_strength import PasswordStrength
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
_: Any
|
_: Any
|
||||||
|
|
||||||
|
|
@ -25,8 +27,9 @@ class User:
|
||||||
}
|
}
|
||||||
|
|
||||||
def display(self) -> str:
|
def display(self) -> str:
|
||||||
password = '*' * len(self.password)
|
strength = PasswordStrength.strength(self.password)
|
||||||
return f'{_("Username")}: {self.username:16} {_("Password")}: {password:16} sudo: {str(self.sudo)}'
|
password = '*' * len(self.password) + f' ({strength.value})'
|
||||||
|
return f'{_("Username")}: {self.username:16} {_("Password")}: {password:20} sudo: {str(self.sudo)}'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parse(cls, config_users: List[Dict[str, Any]]) -> List['User']:
|
def _parse(cls, config_users: List[Dict[str, Any]]) -> List['User']:
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import time
|
||||||
from typing import Any, Optional, TYPE_CHECKING
|
from typing import Any, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from ..menu import Menu
|
from ..menu import Menu
|
||||||
|
from ..models.password_strength import PasswordStrength
|
||||||
from ..output import log
|
from ..output import log
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -16,42 +17,23 @@ if TYPE_CHECKING:
|
||||||
SIG_TRIGGER = None
|
SIG_TRIGGER = None
|
||||||
|
|
||||||
|
|
||||||
def check_password_strong(passwd: str) -> bool:
|
|
||||||
symbol_count = 0
|
|
||||||
if any(character.isdigit() for character in passwd):
|
|
||||||
symbol_count += 10
|
|
||||||
if any(character.isupper() for character in passwd):
|
|
||||||
symbol_count += 26
|
|
||||||
if any(character.islower() for character in passwd):
|
|
||||||
symbol_count += 26
|
|
||||||
if any(not character.isalnum() for character in passwd):
|
|
||||||
symbol_count += 40
|
|
||||||
|
|
||||||
if symbol_count**len(passwd) < 10e20:
|
|
||||||
prompt = str(_("The password you are using seems to be weak, are you sure you want to use it?"))
|
|
||||||
choice = Menu(prompt, Menu.yes_no(), default_option=Menu.yes()).run()
|
|
||||||
return choice.value == Menu.yes()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_password(prompt: str = '') -> Optional[str]:
|
def get_password(prompt: str = '') -> Optional[str]:
|
||||||
if not prompt:
|
if not prompt:
|
||||||
prompt = _("Enter a password: ")
|
prompt = _("Enter a password: ")
|
||||||
|
|
||||||
while passwd := getpass.getpass(prompt):
|
while password := getpass.getpass(prompt):
|
||||||
if len(passwd.strip()) <= 0:
|
if len(password.strip()) <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
if not check_password_strong(passwd):
|
strength = PasswordStrength.strength(password)
|
||||||
continue
|
log(f'Password strength: {strength.value}', fg=strength.color())
|
||||||
|
|
||||||
passwd_verification = getpass.getpass(prompt=_('And one more time for verification: '))
|
passwd_verification = getpass.getpass(prompt=_('And one more time for verification: '))
|
||||||
if passwd != passwd_verification:
|
if password != passwd_verification:
|
||||||
log(' * Passwords did not match * ', fg='red')
|
log(' * Passwords did not match * ', fg='red')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return passwd
|
return password
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue