Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Refactoring Feedback #155

Merged
merged 3 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
121 changes: 16 additions & 105 deletions src/main/java/com/nulabinc/zxcvbn/Feedback.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.nulabinc.zxcvbn;

import com.nulabinc.zxcvbn.guesses.DictionaryGuess;
import com.nulabinc.zxcvbn.matchers.Match;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -68,7 +66,7 @@ public class Feedback {
private final String warning;
private final String[] suggestions;

private Feedback(String warning, String... suggestions) {
Feedback(String warning, String... suggestions) {
this.warning = warning;
this.suggestions = suggestions;
}
Expand Down Expand Up @@ -101,10 +99,11 @@ public List<String> getSuggestions(Locale locale) {
protected ResourceBundle resolveResourceBundle(Locale locale) {
try {
return ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, locale, CONTROL);
} catch (MissingResourceException e) {
} catch (MissingResourceException | UnsupportedOperationException e) {
// MissingResourceException:
// Fix for issue of Android refs: https://github.com/nulab/zxcvbn4j/issues/21
return ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, locale);
} catch (UnsupportedOperationException e) {
//
// UnsupportedOperationException:
// Fix for issue of JDK 9 refs: https://github.com/nulab/zxcvbn4j/issues/45
// ResourceBundle.Control is not supported in named modules.
// See https://docs.oracle.com/javase/9/docs/api/java/util/ResourceBundle.html#bundleprovider
Expand All @@ -127,113 +126,26 @@ private String l10n(ResourceBundle messages, String messageId) {

static Feedback getFeedback(int score, List<Match> sequence) {
if (sequence.size() == 0) {
return getFeedbackWithoutWarnings(
return FeedbackFactory.getFeedbackWithoutWarnings(
DEFAULT_SUGGESTIONS_USE_FEW_WORDS, DEFAULT_SUGGESTIONS_NO_NEED_SYMBOLS);
}
if (score > 2) {
return getEmptyFeedback();
return FeedbackFactory.getEmptyFeedback();
}
Match longestMatch = sequence.get(0);
if (sequence.size() > 1) {
for (Match match : sequence.subList(1, sequence.size())) {
if (match.tokenLength() > longestMatch.tokenLength()) longestMatch = match;
}
}

return getMatchFeedback(longestMatch, sequence.size() == 1);
}

private static Feedback getFeedbackWithoutWarnings(String... suggestions) {
return new Feedback(null, suggestions);
}

private static Feedback getEmptyFeedback() {
return new Feedback(null);
}

private static Feedback getMatchFeedback(Match match, boolean isSoleMatch) {
switch (match.pattern) {
case Dictionary:
return getDictionaryMatchFeedback(match, isSoleMatch);
case Spatial:
return new Feedback(
match.turns == 1
? SPATIAL_WARNING_STRAIGHT_ROWS_OF_KEYS
: SPATIAL_WARNING_SHORT_KEYBOARD_PATTERNS,
EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
SPATIAL_SUGGESTIONS_USE_LONGER_KEYBOARD_PATTERN);
case Repeat:
return new Feedback(
match.baseToken.length() == 1 ? REPEAT_WARNING_LIKE_AAA : REPEAT_WARNING_LIKE_ABCABCABC,
EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
REPEAT_SUGGESTIONS_AVOID_REPEATED_WORDS);
case Sequence:
return new Feedback(
SEQUENCE_WARNING_LIKE_ABCOR6543,
EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
SEQUENCE_SUGGESTIONS_AVOID_SEQUENCES);
case Regex:
return new Feedback(
"recent_year".equals(match.regexName) ? REGEX_WARNING_RECENT_YEARS : null,
EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
REGEX_SUGGESTIONS_AVOID_RECENT_YEARS);
case Date:
return new Feedback(
DATE_WARNING_DATES, EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD, DATE_SUGGESTIONS_AVOID_DATES);
default:
return getFeedbackWithoutWarnings(EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD);
}
}

private static Feedback getDictionaryMatchFeedback(Match match, boolean isSoleMatch) {
String warning = null;
if ("passwords".equals(match.dictionaryName)) {
if (isSoleMatch && !match.l33t && !match.reversed) {
if (match.rank <= 10) {
warning = DICTIONARY_WARNING_PASSWORDS_TOP10;
} else if (match.rank <= 100) {
warning = DICTIONARY_WARNING_PASSWORDS_TOP100;
} else {
warning = DICTIONARY_WARNING_PASSWORDS_VERY_COMMON;
if (match.tokenLength() > longestMatch.tokenLength()) {
longestMatch = match;
}
} else if (match.guessesLog10 <= 4) {
warning = DICTIONARY_WARNING_PASSWORDS_SIMILAR;
}
} else if ("english_wikipedia".equals(match.dictionaryName)) {
if (isSoleMatch) {
warning = DICTIONARY_WARNING_ENGLISH_WIKIPEDIA_ITSELF;
}
} else if (Arrays.asList(new String[] {"surnames", "male_names", "female_names"})
.contains(match.dictionaryName)) {
if (isSoleMatch) {
warning = DICTIONARY_WARNING_ETC_NAMES_THEMSELVES;
} else {
warning = DICTIONARY_WARNING_ETC_NAMES_COMMON;
}
}

List<String> suggestions = new ArrayList<>();
suggestions.add(EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD);

CharSequence word = match.token;
WipeableString lower = WipeableString.lowerCase(word);
if (DictionaryGuess.START_UPPER.matcher(word).find()) {
suggestions.add(DICTIONARY_SUGGESTIONS_CAPITALIZATION);
} else if (DictionaryGuess.ALL_UPPER.matcher(word).find() && !lower.equals(word)) {
suggestions.add(DICTIONARY_SUGGESTIONS_ALL_UPPERCASE);
}
if (match.reversed && match.tokenLength() >= 4) {
suggestions.add(DICTIONARY_SUGGESTIONS_REVERSED);
}
if (match.l33t) {
suggestions.add(DICTIONARY_SUGGESTIONS_L33T);
}
lower.wipe();
return new Feedback(warning, suggestions.toArray(new String[suggestions.size()]));
boolean isSoleMatch = sequence.size() == 1;
return FeedbackFactory.createMatchFeedback(longestMatch, isSoleMatch);
}

private static class ResourceBundleFeedback extends Feedback {
private ResourceBundle messages;
private final ResourceBundle messages;

private ResourceBundleFeedback(ResourceBundle messages, String warning, String... suggestions) {
super(warning, suggestions);
Expand All @@ -258,13 +170,12 @@ private ReplacedMessagesFeedback(
@Override
protected ResourceBundle resolveResourceBundle(Locale locale) {
try {
if (messages.containsKey(locale)) {
return messages.get(locale);
ResourceBundle resource = messages.get(locale);
if (resource != null) {
return resource;
}
return ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, locale, CONTROL);
} catch (MissingResourceException e) {
return ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, locale);
} catch (UnsupportedOperationException e) {
} catch (MissingResourceException | UnsupportedOperationException e) {
return ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, locale);
}
}
Expand Down
157 changes: 157 additions & 0 deletions src/main/java/com/nulabinc/zxcvbn/FeedbackFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.nulabinc.zxcvbn;

import com.nulabinc.zxcvbn.guesses.DictionaryGuess;
import com.nulabinc.zxcvbn.matchers.Match;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class FeedbackFactory {

private static final List<String> NAME_DICTIONARIES =
Arrays.asList("surnames", "male_names", "female_names");

static Feedback getFeedbackWithoutWarnings(String... suggestions) {
return new Feedback(null, suggestions);
}

static Feedback getEmptyFeedback() {
return new Feedback(null);
}

static Feedback createMatchFeedback(Match match, boolean isSoleMatch) {
switch (match.pattern) {
case Dictionary:
return createDictionaryMatchFeedback(match, isSoleMatch);
case Spatial:
return createSpatialMatchFeedback(match);
case Repeat:
return createRepeatMatchFeedback(match);
case Sequence:
return createSequenceMatchFeedback();
case Regex:
return createRegexMatchFeedback(match);
case Date:
return createDateMatchFeedback();
default:
return getFeedbackWithoutWarnings(Feedback.EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD);
}
}

private static Feedback createSpatialMatchFeedback(Match match) {
String warning =
match.turns == 1
? Feedback.SPATIAL_WARNING_STRAIGHT_ROWS_OF_KEYS
: Feedback.SPATIAL_WARNING_SHORT_KEYBOARD_PATTERNS;
return new Feedback(
warning,
Feedback.EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
Feedback.SPATIAL_SUGGESTIONS_USE_LONGER_KEYBOARD_PATTERN);
}

private static Feedback createRepeatMatchFeedback(Match match) {
String warning =
match.baseToken.length() == 1
? Feedback.REPEAT_WARNING_LIKE_AAA
: Feedback.REPEAT_WARNING_LIKE_ABCABCABC;
return new Feedback(
warning,
Feedback.EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
Feedback.REPEAT_SUGGESTIONS_AVOID_REPEATED_WORDS);
}

private static Feedback createSequenceMatchFeedback() {
return new Feedback(
Feedback.SEQUENCE_WARNING_LIKE_ABCOR6543,
Feedback.EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
Feedback.SEQUENCE_SUGGESTIONS_AVOID_SEQUENCES);
}

private static Feedback createRegexMatchFeedback(Match match) {
String warning =
"recent_year".equals(match.regexName) ? Feedback.REGEX_WARNING_RECENT_YEARS : null;
return new Feedback(
warning,
Feedback.EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
Feedback.REGEX_SUGGESTIONS_AVOID_RECENT_YEARS);
}

private static Feedback createDateMatchFeedback() {
return new Feedback(
Feedback.DATE_WARNING_DATES,
Feedback.EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD,
Feedback.DATE_SUGGESTIONS_AVOID_DATES);
}

private static Feedback createDictionaryMatchFeedback(Match match, boolean isSoleMatch) {
String warning = getWarningBasedOnMatch(match, isSoleMatch);
List<String> suggestions = generateSuggestions(match);
return new Feedback(warning, suggestions.toArray(new String[0]));
}

private static String getWarningBasedOnMatch(Match match, boolean isSoleMatch) {
if ("passwords".equals(match.dictionaryName)) {
return getPasswordWarning(match, isSoleMatch);
}

if ("english_wikipedia".equals(match.dictionaryName) && isSoleMatch) {
return Feedback.DICTIONARY_WARNING_ENGLISH_WIKIPEDIA_ITSELF;
}

if (NAME_DICTIONARIES.contains(match.dictionaryName)) {
return getNameDictionaryWarning(isSoleMatch);
}

return null;
}

private static String getPasswordWarning(Match match, boolean isSoleMatch) {
if (isSoleMatch && !match.l33t && !match.reversed) {
if (match.rank <= 10) {
return Feedback.DICTIONARY_WARNING_PASSWORDS_TOP10;
}
if (match.rank <= 100) {
return Feedback.DICTIONARY_WARNING_PASSWORDS_TOP100;
}
return Feedback.DICTIONARY_WARNING_PASSWORDS_VERY_COMMON;
}
if (match.guessesLog10 <= 4) {
return Feedback.DICTIONARY_WARNING_PASSWORDS_SIMILAR;
}
return null;
}

private static String getNameDictionaryWarning(boolean isSoleMatch) {
if (isSoleMatch) {
return Feedback.DICTIONARY_WARNING_ETC_NAMES_THEMSELVES;
}
return Feedback.DICTIONARY_WARNING_ETC_NAMES_COMMON;
}

private static List<String> generateSuggestions(Match match) {
List<String> suggestions = new ArrayList<>();
suggestions.add(Feedback.EXTRA_SUGGESTIONS_ADD_ANOTHER_WORD);

CharSequence word = match.token;
WipeableString lower = WipeableString.lowerCase(word);

if (DictionaryGuess.START_UPPER.matcher(word).find()) {
suggestions.add(Feedback.DICTIONARY_SUGGESTIONS_CAPITALIZATION);
}

if (DictionaryGuess.ALL_UPPER.matcher(word).find() && !lower.equals(word)) {
suggestions.add(Feedback.DICTIONARY_SUGGESTIONS_ALL_UPPERCASE);
}

if (match.reversed && match.tokenLength() >= 4) {
suggestions.add(Feedback.DICTIONARY_SUGGESTIONS_REVERSED);
}

if (match.l33t) {
suggestions.add(Feedback.DICTIONARY_SUGGESTIONS_L33T);
}

lower.wipe();
return suggestions;
}
}