diff --git a/src/Feedback.php b/src/Feedback.php index 43349ec..8d633d0 100644 --- a/src/Feedback.php +++ b/src/Feedback.php @@ -12,6 +12,22 @@ */ class Feedback { + const FEEDBACK_CODE_EMPTY = 'empty'; + const FEEDBACK_CODE_COMMON = 'common'; + const FEEDBACK_CODE_COMMON_SIMILAR = 'common_similar'; + const FEEDBACK_CODE_COMMON_TOP_10 = 'common_top_10'; + const FEEDBACK_CODE_COMMON_TOP_100 = 'common_top_100'; + const FEEDBACK_CODE_GUESSABLE_DATES = 'guessable_dates'; + const FEEDBACK_CODE_GUESSABLE_NAME = 'guessable_name'; + const FEEDBACK_CODE_GUESSABLE_NAMES = 'guessable_names'; + const FEEDBACK_CODE_GUESSABLE_REPEATED_CHARACTER = 'guessable_repeated_character'; + const FEEDBACK_CODE_GUESSABLE_REPEATED_STRING = 'guessable_repeated_string'; + const FEEDBACK_CODE_GUESSABLE_SEQUENCE = 'guessable_sequence'; + const FEEDBACK_CODE_GUESSABLE_SPATIAL_ROW = 'guessable_spatial_row'; + const FEEDBACK_CODE_GUESSABLE_SPATIAL_PATTERN = 'guessable_spatial_pattern'; + const FEEDBACK_CODE_GUESSABLE_WORD = 'guessable_word'; + const FEEDBACK_CODE_GUESSABLE_YEARS = 'guessable_years'; + /** * @param int $score * @param MatchInterface[] $sequence @@ -22,6 +38,7 @@ public function getFeedback($score, array $sequence) // starting feedback if (count($sequence) === 0) { return [ + 'code' => static::FEEDBACK_CODE_EMPTY, 'warning' => '', 'suggestions' => [ "Use a few words, avoid common phrases", @@ -33,6 +50,7 @@ public function getFeedback($score, array $sequence) // no feedback if score is good or great. if ($score > 2) { return [ + 'code' => '', 'warning' => '', 'suggestions' => [], ]; diff --git a/src/Matchers/Bruteforce.php b/src/Matchers/Bruteforce.php index 5561a78..a015b48 100644 --- a/src/Matchers/Bruteforce.php +++ b/src/Matchers/Bruteforce.php @@ -33,6 +33,7 @@ public static function match($password, array $userInputs = []) public function getFeedback($isSoleMatch) { return [ + 'code' => "", 'warning' => "", 'suggestions' => [ ] diff --git a/src/Matchers/DateMatch.php b/src/Matchers/DateMatch.php index a27bd89..ebeb612 100644 --- a/src/Matchers/DateMatch.php +++ b/src/Matchers/DateMatch.php @@ -2,6 +2,7 @@ namespace ZxcvbnPhp\Matchers; +use ZxcvbnPhp\Feedback; use ZxcvbnPhp\Matcher; class DateMatch extends BaseMatch @@ -108,6 +109,7 @@ public static function match($password, array $userInputs = []) public function getFeedback($isSoleMatch) { return [ + 'code' => Feedback::FEEDBACK_CODE_GUESSABLE_DATES, 'warning' => "Dates are often easy to guess", 'suggestions' => [ 'Avoid dates and years that are associated with you' diff --git a/src/Matchers/DictionaryMatch.php b/src/Matchers/DictionaryMatch.php index 970c9ed..9ba687d 100644 --- a/src/Matchers/DictionaryMatch.php +++ b/src/Matchers/DictionaryMatch.php @@ -2,6 +2,7 @@ namespace ZxcvbnPhp\Matchers; +use ZxcvbnPhp\Feedback; use ZxcvbnPhp\Matcher; class DictionaryMatch extends BaseMatch @@ -89,8 +90,11 @@ public function getFeedback($isSoleMatch) $startUpper = '/^[A-Z][^A-Z]+$/u'; $allUpper = '/^[^a-z]+$/u'; + list($code, $warning) = $this->getFeedbackWarning($isSoleMatch); + $feedback = [ - 'warning' => $this->getFeedbackWarning($isSoleMatch), + 'code' => $code, + 'warning' => $warning, 'suggestions' => [] ]; @@ -109,32 +113,53 @@ public function getFeedbackWarning($isSoleMatch) case 'passwords': if ($isSoleMatch && !$this->l33t && !$this->reversed) { if ($this->rank <= 10) { - return 'This is a top-10 common password'; + return [ + Feedback::FEEDBACK_CODE_COMMON_TOP_10, + 'This is a top-10 common password', + ]; } elseif ($this->rank <= 100) { - return 'This is a top-100 common password'; + return [ + Feedback::FEEDBACK_CODE_COMMON_TOP_100, + 'This is a top-100 common password', + ]; } else { - return 'This is a very common password'; + return [ + Feedback::FEEDBACK_CODE_COMMON, + 'This is a very common password' + ]; } } elseif ($this->getGuessesLog10() <= 4) { - return 'This is similar to a commonly used password'; + return [ + Feedback::FEEDBACK_CODE_COMMON_SIMILAR, + 'This is similar to a commonly used password', + ]; } break; case 'english_wikipedia': if ($isSoleMatch) { - return 'A word by itself is easy to guess'; + return [ + Feedback::FEEDBACK_CODE_GUESSABLE_WORD, + 'A word by itself is easy to guess', + ]; } break; case 'surnames': case 'male_names': case 'female_names': if ($isSoleMatch) { - return 'Names and surnames by themselves are easy to guess'; + return [ + Feedback::FEEDBACK_CODE_GUESSABLE_NAME, + 'Names and surnames by themselves are easy to guess', + ]; } else { - return 'Common names and surnames are easy to guess'; + return [ + Feedback::FEEDBACK_CODE_GUESSABLE_NAMES, + 'Common names and surnames are easy to guess', + ]; } } - return ''; + return ['', '']; } /** diff --git a/src/Matchers/RepeatMatch.php b/src/Matchers/RepeatMatch.php index 8b74519..a6968a4 100644 --- a/src/Matchers/RepeatMatch.php +++ b/src/Matchers/RepeatMatch.php @@ -2,6 +2,7 @@ namespace ZxcvbnPhp\Matchers; +use ZxcvbnPhp\Feedback; use ZxcvbnPhp\Matcher; use ZxcvbnPhp\Scorer; @@ -88,7 +89,12 @@ public function getFeedback($isSoleMatch) ? 'Repeats like "aaa" are easy to guess' : 'Repeats like "abcabcabc" are only slightly harder to guess than "abc"'; + $code = mb_strlen($this->repeatedChar) == 1 + ? Feedback::FEEDBACK_CODE_GUESSABLE_REPEATED_CHARACTER + : Feedback::FEEDBACK_CODE_GUESSABLE_REPEATED_STRING; + return [ + 'code' => $code, 'warning' => $warning, 'suggestions' => [ 'Avoid repeated words and characters', diff --git a/src/Matchers/SequenceMatch.php b/src/Matchers/SequenceMatch.php index 3a55ede..d46015b 100644 --- a/src/Matchers/SequenceMatch.php +++ b/src/Matchers/SequenceMatch.php @@ -2,6 +2,8 @@ namespace ZxcvbnPhp\Matchers; +use ZxcvbnPhp\Feedback; + class SequenceMatch extends BaseMatch { public const MAX_DELTA = 5; @@ -87,6 +89,7 @@ public static function findSequenceMatch($password, $begin, $end, $delta, &$matc public function getFeedback($isSoleMatch) { return [ + 'code' => Feedback::FEEDBACK_CODE_GUESSABLE_SEQUENCE, 'warning' => "Sequences like abc or 6543 are easy to guess", 'suggestions' => [ 'Avoid sequences' diff --git a/src/Matchers/SpatialMatch.php b/src/Matchers/SpatialMatch.php index b181c96..1b3882b 100644 --- a/src/Matchers/SpatialMatch.php +++ b/src/Matchers/SpatialMatch.php @@ -2,6 +2,7 @@ namespace ZxcvbnPhp\Matchers; +use ZxcvbnPhp\Feedback; use ZxcvbnPhp\Matcher; class SpatialMatch extends BaseMatch @@ -60,7 +61,12 @@ public function getFeedback($isSoleMatch) ? 'Straight rows of keys are easy to guess' : 'Short keyboard patterns are easy to guess'; + $code = $this->turns == 1 + ? Feedback::FEEDBACK_CODE_GUESSABLE_SPATIAL_ROW + : Feedback::FEEDBACK_CODE_GUESSABLE_SPATIAL_PATTERN; + return [ + 'code' => $code, 'warning' => $warning, 'suggestions' => [ 'Use a longer keyboard pattern with more turns' diff --git a/src/Matchers/YearMatch.php b/src/Matchers/YearMatch.php index dfd8c73..cb28200 100644 --- a/src/Matchers/YearMatch.php +++ b/src/Matchers/YearMatch.php @@ -2,6 +2,7 @@ namespace ZxcvbnPhp\Matchers; +use ZxcvbnPhp\Feedback; use ZxcvbnPhp\Matcher; class YearMatch extends BaseMatch @@ -33,6 +34,7 @@ public static function match($password, array $userInputs = []) public function getFeedback($isSoleMatch) { return [ + 'code' => Feedback::FEEDBACK_CODE_GUESSABLE_YEARS, 'warning' => "Recent years are easy to guess", 'suggestions' => [ 'Avoid recent years', diff --git a/test/FeedbackTest.php b/test/FeedbackTest.php index 06ccbf0..dfc5691 100644 --- a/test/FeedbackTest.php +++ b/test/FeedbackTest.php @@ -33,6 +33,11 @@ public function testFeedbackForEmptyPassword() $feedback['suggestions'], "default suggestion #1" ); + $this->assertEquals( + 'empty', + $feedback['code'], + "default warning code" + ); } public function testHighScoringSequence() @@ -42,6 +47,7 @@ public function testHighScoringSequence() $this->assertEquals('', $feedback['warning'], "no warning for good score"); $this->assertEmpty($feedback['suggestions'], "no suggestions for good score"); + $this->assertEquals('', $feedback['code'], "no code for good score"); } public function testLongestMatchGetsFeedback() @@ -70,6 +76,11 @@ public function testLongestMatchGetsFeedback() $feedback['suggestions'], "no suggestion provided for the shorter match" ); + $this->assertEquals( + 'guessable_dates', + $feedback['code'], + "code provided for the longest match" + ); } public function testDefaultSuggestion() diff --git a/test/Matchers/DateTest.php b/test/Matchers/DateTest.php index 1a3b370..4f9cccb 100644 --- a/test/Matchers/DateTest.php +++ b/test/Matchers/DateTest.php @@ -2,6 +2,7 @@ namespace ZxcvbnPhp\Test\Matchers; +use ZxcvbnPhp\Feedback; use ZxcvbnPhp\Matchers\DateMatch; class DateTest extends AbstractMatchTest @@ -311,5 +312,10 @@ public function testFeedback() $feedback['suggestions'], "date match gives correct suggestion" ); + $this->assertEquals( + 'guessable_dates', + $feedback['code'], + "date match gives correct code" + ); } } diff --git a/test/Matchers/DictionaryTest.php b/test/Matchers/DictionaryTest.php index d3fadf3..3d26238 100644 --- a/test/Matchers/DictionaryTest.php +++ b/test/Matchers/DictionaryTest.php @@ -266,6 +266,11 @@ public function testGuessesUppercaseVariations($token, $expectedGuesses) public function testFeedbackTop10Password() { $feedback = $this->getFeedbackForToken('password', 'passwords', 2, true); + $this->assertEquals( + 'common_top_10', + $feedback['code'], + 'dictionary match returns code for top-10 password' + ); $this->assertEquals( 'This is a top-10 common password', $feedback['warning'], @@ -276,6 +281,11 @@ public function testFeedbackTop10Password() public function testFeedbackTop100Password() { $feedback = $this->getFeedbackForToken('hunter', 'passwords', 37, true); + $this->assertEquals( + 'common_top_100', + $feedback['code'], + 'dictionary match returns code for top-100 password' + ); $this->assertEquals( 'This is a top-100 common password', $feedback['warning'], @@ -286,6 +296,11 @@ public function testFeedbackTop100Password() public function testFeedbackTopPasswordSoleMatch() { $feedback = $this->getFeedbackForToken('mytruck', 'passwords', 19324, true); + $this->assertEquals( + 'common', + $feedback['code'], + 'dictionary match returns code for common password' + ); $this->assertEquals( 'This is a very common password', $feedback['warning'], @@ -296,6 +311,11 @@ public function testFeedbackTopPasswordSoleMatch() public function testFeedbackTopPasswordNotSoleMatch() { $feedback = $this->getFeedbackForToken('browndog', 'passwords', 7014, false); + $this->assertEquals( + 'common_similar', + $feedback['code'], + 'dictionary match returns code for common password (not a sole match)' + ); $this->assertEquals( 'This is similar to a commonly used password', $feedback['warning'], @@ -306,6 +326,11 @@ public function testFeedbackTopPasswordNotSoleMatch() public function testFeedbackTopPasswordNotSoleMatchRankTooLow() { $feedback = $this->getFeedbackForToken('mytruck', 'passwords', 19324, false); + $this->assertEquals( + '', + $feedback['code'], + 'no warning code for a non-sole match in the password dictionary' + ); $this->assertEquals( '', $feedback['warning'], @@ -316,6 +341,11 @@ public function testFeedbackTopPasswordNotSoleMatchRankTooLow() public function testFeedbackWikipediaWordSoleMatch() { $feedback = $this->getFeedbackForToken('university', 'english_wikipedia', 69, true); + $this->assertEquals( + 'guessable_word', + $feedback['code'], + 'dictionary match returns code for Wikipedia word (sole match)' + ); $this->assertEquals( 'A word by itself is easy to guess', $feedback['warning'], @@ -326,6 +356,11 @@ public function testFeedbackWikipediaWordSoleMatch() public function testFeedbackWikipediaWordNonSoleMatch() { $feedback = $this->getFeedbackForToken('university', 'english_wikipedia', 69, false); + $this->assertEquals( + '', + $feedback['code'], + 'dictionary match doesn\'t rerurn code for Wikipedia word (not a sole match)' + ); $this->assertEquals( '', $feedback['warning'], @@ -336,6 +371,11 @@ public function testFeedbackWikipediaWordNonSoleMatch() public function testFeedbackNameSoleMatch() { $feedback = $this->getFeedbackForToken('rodriguez', 'surnames', 21, true); + $this->assertEquals( + 'guessable_name', + $feedback['code'], + 'dictionary match returns code for surname (sole match)' + ); $this->assertEquals( 'Names and surnames by themselves are easy to guess', $feedback['warning'], @@ -346,6 +386,11 @@ public function testFeedbackNameSoleMatch() public function testFeedbackNameNonSoleMatch() { $feedback = $this->getFeedbackForToken('rodriguez', 'surnames', 21, false); + $this->assertEquals( + 'guessable_names', + $feedback['code'], + 'dictionary match returns code for surname (not a sole match)' + ); $this->assertEquals( 'Common names and surnames are easy to guess', $feedback['warning'], @@ -356,6 +401,11 @@ public function testFeedbackNameNonSoleMatch() public function testFeedbackTvAndFilmDictionary() { $feedback = $this->getFeedbackForToken('know', 'us_tv_and_film', 9, true); + $this->assertEquals( + '', + $feedback['code'], + 'dictionary match returns code for match from us_tv_and_film dictionary' + ); $this->assertEquals( '', $feedback['warning'], diff --git a/test/Matchers/L33tTest.php b/test/Matchers/L33tTest.php index 6f76d11..b4aac80 100644 --- a/test/Matchers/L33tTest.php +++ b/test/Matchers/L33tTest.php @@ -355,6 +355,11 @@ public function testFeedback() $feedback['suggestions'], "l33t match gives correct suggestion" ); + $this->assertEquals( + 'guessable_word', + $feedback['code'], + "l33t match gives correct code" + ); } public function testFeedbackTop100Password() diff --git a/test/Matchers/RepeatTest.php b/test/Matchers/RepeatTest.php index b0595d8..567dcb0 100644 --- a/test/Matchers/RepeatTest.php +++ b/test/Matchers/RepeatTest.php @@ -291,6 +291,11 @@ public function testFeedbackSingleCharacterRepeat() $feedback['suggestions'], "one repeated character gives correct suggestion" ); + $this->assertEquals( + 'guessable_repeated_character', + $feedback['code'], + "one repeated character gives correct code" + ); } public function testFeedbackMultipleCharacterRepeat() @@ -312,5 +317,10 @@ public function testFeedbackMultipleCharacterRepeat() $feedback['suggestions'], "multiple repeated characters gives correct suggestion" ); + $this->assertEquals( + 'guessable_repeated_string', + $feedback['code'], + "multiple repeated characters gives correct code" + ); } } diff --git a/test/Matchers/ReverseDictionaryTest.php b/test/Matchers/ReverseDictionaryTest.php index 02244fc..ebb2b83 100644 --- a/test/Matchers/ReverseDictionaryTest.php +++ b/test/Matchers/ReverseDictionaryTest.php @@ -60,6 +60,11 @@ public function testFeedback() $feedback['suggestions'], "reverse dictionary match gives correct suggestion" ); + $this->assertEquals( + 'guessable_word', + $feedback['code'], + "reverse dictionary match gives correct code" + ); } public function testFeedbackTop100Password() @@ -76,6 +81,11 @@ public function testFeedbackTop100Password() $feedback['warning'], "reverse dictionary match doesn't give top-100 warning" ); + $this->assertEquals( + 'common_similar', + $feedback['code'], + "reverse dictionary match doesn't give top-100 code" + ); } public function testFeedbackShortToken() @@ -97,5 +107,10 @@ public function testFeedbackShortToken() $feedback['suggestions'], "reverse dictionary match doesn't give suggestion for short token" ); + $this->assertEquals( + 'guessable_word', + $feedback['code'], + "reverse dictionary match doesn't give code for short token" + ); } } diff --git a/test/Matchers/SequenceTest.php b/test/Matchers/SequenceTest.php index 600a63c..5c12491 100644 --- a/test/Matchers/SequenceTest.php +++ b/test/Matchers/SequenceTest.php @@ -205,5 +205,10 @@ public function testFeedback() $feedback['suggestions'], "sequence gives correct suggestion" ); + $this->assertEquals( + 'guessable_sequence', + $feedback['code'], + "sequence gives correct code" + ); } } diff --git a/test/Matchers/YearTest.php b/test/Matchers/YearTest.php index 2b6cebd..67dbd82 100644 --- a/test/Matchers/YearTest.php +++ b/test/Matchers/YearTest.php @@ -153,5 +153,10 @@ public function testFeedback() $feedback['suggestions'], "year match gives correct suggestion #2" ); + $this->assertEquals( + 'guessable_years', + $feedback['code'], + "year match gives correct code" + ); } }