frappe/env/lib/python3.12/site-packages/zxcvbn/feedback.py

135 lines
4.6 KiB
Python

from zxcvbn.scoring import START_UPPER, ALL_UPPER
from gettext import gettext as _
def get_feedback(score, sequence):
if len(sequence) == 0:
return {
'warning': '',
'suggestions': [
_("Use a few words, avoid common phrases."),
_("No need for symbols, digits, or uppercase letters.")
]
}
if score > 2:
return {
'warning': '',
'suggestions': [],
}
longest_match = sequence[0]
for match in sequence[1:]:
if len(match['token']) > len(longest_match['token']):
longest_match = match
feedback = get_match_feedback(longest_match, len(sequence) == 1)
extra_feedback = _('Add another word or two. Uncommon words are better.')
if feedback:
feedback['suggestions'].insert(0, extra_feedback)
if not feedback['warning']:
feedback['warning'] = ''
else:
feedback = {
'warning': '',
'suggestions': [extra_feedback]
}
return feedback
def get_match_feedback(match, is_sole_match):
if match['pattern'] == 'dictionary':
return get_dictionary_match_feedback(match, is_sole_match)
elif match['pattern'] == 'spatial':
if match['turns'] == 1:
warning = _('Straight rows of keys are easy to guess.')
else:
warning = _('Short keyboard patterns are easy to guess.')
return {
'warning': warning,
'suggestions': [
_('Use a longer keyboard pattern with more turns.')
]
}
elif match['pattern'] == 'repeat':
if len(match['base_token']) == 1:
warning = _('Repeats like "aaa" are easy to guess.')
else:
warning = _('Repeats like "abcabcabc" are only slightly harder to ' \
'guess than "abc".')
return {
'warning': warning,
'suggestions': [
_('Avoid repeated words and characters.')
]
}
elif match['pattern'] == 'sequence':
return {
'warning': _('Sequences like "abc" or "6543" are easy to guess.'),
'suggestions': [
_('Avoid sequences.')
]
}
elif match['pattern'] == 'regex':
if match['regex_name'] == 'recent_year':
return {
'warning': _("Recent years are easy to guess."),
'suggestions': [
_('Avoid recent years.'),
_('Avoid years that are associated with you.'),
]
}
elif match['pattern'] == 'date':
return {
'warning': _("Dates are often easy to guess."),
'suggestions': [
_('Avoid dates and years that are associated with you.'),
],
}
def get_dictionary_match_feedback(match, is_sole_match):
warning = ''
if match['dictionary_name'] == 'passwords':
if is_sole_match and not match.get('l33t', False) and not \
match['reversed']:
if match['rank'] <= 10:
warning = _('This is a top-10 common password.')
elif match['rank'] <= 100:
warning = _('This is a top-100 common password.')
else:
warning = _('This is a very common password.')
elif match['guesses_log10'] <= 4:
warning = _('This is similar to a commonly used password.')
elif match['dictionary_name'] == 'english':
if is_sole_match:
warning = _('A word by itself is easy to guess.')
elif match['dictionary_name'] in ['surnames', 'male_names',
'female_names', ]:
if is_sole_match:
warning = _('Names and surnames by themselves are easy to guess.')
else:
warning = _('Common names and surnames are easy to guess.')
else:
warning = ''
suggestions = []
word = match['token']
if START_UPPER.search(word):
suggestions.append(_("Capitalization doesn't help very much."))
elif ALL_UPPER.search(word) and word.lower() != word:
suggestions.append(_("All-uppercase is almost as easy to guess as "
"all-lowercase."))
if match['reversed'] and len(match['token']) >= 4:
suggestions.append(_("Reversed words aren't much harder to guess."))
if match.get('l33t', False):
suggestions.append(_("Predictable substitutions like '@' instead of 'a' "
"don't help very much."))
return {
'warning': warning,
'suggestions': suggestions,
}