Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/Feedback.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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' => [],
];
Expand Down
1 change: 1 addition & 0 deletions src/Matchers/Bruteforce.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static function match($password, array $userInputs = [])
public function getFeedback($isSoleMatch)
{
return [
'code' => "",
'warning' => "",
'suggestions' => [
]
Expand Down
2 changes: 2 additions & 0 deletions src/Matchers/DateMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Feedback;
use ZxcvbnPhp\Matcher;

class DateMatch extends BaseMatch
Expand Down Expand Up @@ -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'
Expand Down
43 changes: 34 additions & 9 deletions src/Matchers/DictionaryMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Feedback;
use ZxcvbnPhp\Matcher;

class DictionaryMatch extends BaseMatch
Expand Down Expand Up @@ -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' => []
];

Expand All @@ -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 ['', ''];
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/Matchers/RepeatMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Feedback;
use ZxcvbnPhp\Matcher;
use ZxcvbnPhp\Scorer;

Expand Down Expand Up @@ -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',
Expand Down
3 changes: 3 additions & 0 deletions src/Matchers/SequenceMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Feedback;

class SequenceMatch extends BaseMatch
{
public const MAX_DELTA = 5;
Expand Down Expand Up @@ -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'
Expand Down
6 changes: 6 additions & 0 deletions src/Matchers/SpatialMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Feedback;
use ZxcvbnPhp\Matcher;

class SpatialMatch extends BaseMatch
Expand Down Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions src/Matchers/YearMatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ZxcvbnPhp\Matchers;

use ZxcvbnPhp\Feedback;
use ZxcvbnPhp\Matcher;

class YearMatch extends BaseMatch
Expand Down Expand Up @@ -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',
Expand Down
11 changes: 11 additions & 0 deletions test/FeedbackTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public function testFeedbackForEmptyPassword()
$feedback['suggestions'],
"default suggestion #1"
);
$this->assertEquals(
'empty',
$feedback['code'],
"default warning code"
);
}

public function testHighScoringSequence()
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions test/Matchers/DateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ZxcvbnPhp\Test\Matchers;

use ZxcvbnPhp\Feedback;
use ZxcvbnPhp\Matchers\DateMatch;

class DateTest extends AbstractMatchTest
Expand Down Expand Up @@ -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"
);
}
}
50 changes: 50 additions & 0 deletions test/Matchers/DictionaryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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'],
Expand All @@ -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'],
Expand All @@ -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'],
Expand All @@ -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'],
Expand All @@ -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'],
Expand All @@ -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'],
Expand All @@ -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'],
Expand All @@ -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'],
Expand All @@ -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'],
Expand Down
Loading