diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 640ab85..0d164b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: - '7.2' - '7.3' - '7.4' + - '8.0' name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} steps: - name: Checkout diff --git a/composer.json b/composer.json index 42cfdb5..ec189c1 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "php": "^7.2", + "php": "^7.2 | ^8.0", "symfony/polyfill-mbstring": ">=1.3.1" }, "require-dev": { diff --git a/src/Feedback.php b/src/Feedback.php index de73e08..43349ec 100644 --- a/src/Feedback.php +++ b/src/Feedback.php @@ -2,7 +2,7 @@ namespace ZxcvbnPhp; -use ZxcvbnPhp\Matchers\Match; +use ZxcvbnPhp\Matchers\MatchInterface; /** * Feedback - gives some user guidance based on the strength @@ -14,7 +14,7 @@ class Feedback { /** * @param int $score - * @param Match[] $sequence + * @param MatchInterface[] $sequence * @return array */ public function getFeedback($score, array $sequence) @@ -22,19 +22,19 @@ public function getFeedback($score, array $sequence) // starting feedback if (count($sequence) === 0) { return [ - 'warning' => '', + 'warning' => '', 'suggestions' => [ "Use a few words, avoid common phrases", - "No need for symbols, digits, or uppercase letters" - ] + "No need for symbols, digits, or uppercase letters", + ], ]; } // no feedback if score is good or great. if ($score > 2) { return [ - 'warning' => '', - 'suggestions' => [] + 'warning' => '', + 'suggestions' => [], ]; } diff --git a/src/Matcher.php b/src/Matcher.php index ea27985..110ca0b 100644 --- a/src/Matcher.php +++ b/src/Matcher.php @@ -2,7 +2,7 @@ namespace ZxcvbnPhp; -use ZxcvbnPhp\Matchers\Match; +use ZxcvbnPhp\Matchers\BaseMatch; use ZxcvbnPhp\Matchers\MatchInterface; class Matcher @@ -23,14 +23,14 @@ class Matcher /** * Get matches for a password. * - * @see zxcvbn/src/matching.coffee::omnimatch - * - * @param string $password Password string to match - * @param array $userInputs Array of values related to the user (optional) + * @param string $password Password string to match + * @param array $userInputs Array of values related to the user (optional) * @code array('Alice Smith') * @endcode * - * @return Match[] Array of Match objects. + * @return MatchInterface[] Array of Match objects. + * + * @see zxcvbn/src/matching.coffee::omnimatch */ public function getMatches($password, array $userInputs = []) { @@ -77,7 +77,7 @@ public static function usortStable(array &$array, callable $value_compare_func) { $index = 0; foreach ($array as &$item) { - $item = array($index++, $item); + $item = [$index++, $item]; } $result = usort($array, function ($a, $b) use ($value_compare_func) { $result = $value_compare_func($a[1], $b[1]); @@ -89,7 +89,7 @@ public static function usortStable(array &$array, callable $value_compare_func) return $result; } - public static function compareMatches(Match $a, Match $b) + public static function compareMatches(BaseMatch $a, BaseMatch $b) { $beginDiff = $a->begin - $b->begin; if ($beginDiff) { diff --git a/src/Matchers/Match.php b/src/Matchers/BaseMatch.php similarity index 98% rename from src/Matchers/Match.php rename to src/Matchers/BaseMatch.php index 2fd2f1b..d0f1e32 100644 --- a/src/Matchers/Match.php +++ b/src/Matchers/BaseMatch.php @@ -4,7 +4,7 @@ use ZxcvbnPhp\Scorer; -abstract class Match implements MatchInterface +abstract class BaseMatch implements MatchInterface { /** diff --git a/src/Matchers/Bruteforce.php b/src/Matchers/Bruteforce.php index 2c34b7d..5561a78 100644 --- a/src/Matchers/Bruteforce.php +++ b/src/Matchers/Bruteforce.php @@ -10,7 +10,7 @@ * * Intentionally not named with Match suffix to prevent autoloading from Matcher. */ -class Bruteforce extends Match +class Bruteforce extends BaseMatch { public const BRUTEFORCE_CARDINALITY = 10; diff --git a/src/Matchers/DateMatch.php b/src/Matchers/DateMatch.php index 22c0a4e..a27bd89 100644 --- a/src/Matchers/DateMatch.php +++ b/src/Matchers/DateMatch.php @@ -4,7 +4,7 @@ use ZxcvbnPhp\Matcher; -class DateMatch extends Match +class DateMatch extends BaseMatch { public const NUM_YEARS = 119; // Years match against 1900 - 2019 public const NUM_MONTHS = 12; diff --git a/src/Matchers/DictionaryMatch.php b/src/Matchers/DictionaryMatch.php index a087155..970c9ed 100644 --- a/src/Matchers/DictionaryMatch.php +++ b/src/Matchers/DictionaryMatch.php @@ -4,7 +4,7 @@ use ZxcvbnPhp\Matcher; -class DictionaryMatch extends Match +class DictionaryMatch extends BaseMatch { public $pattern = 'dictionary'; diff --git a/src/Matchers/RepeatMatch.php b/src/Matchers/RepeatMatch.php index dc04369..8b74519 100644 --- a/src/Matchers/RepeatMatch.php +++ b/src/Matchers/RepeatMatch.php @@ -5,7 +5,7 @@ use ZxcvbnPhp\Matcher; use ZxcvbnPhp\Scorer; -class RepeatMatch extends Match +class RepeatMatch extends BaseMatch { public const GREEDY_MATCH = '/(.+)\1+/u'; public const LAZY_MATCH = '/(.+?)\1+/u'; @@ -13,7 +13,7 @@ class RepeatMatch extends Match public $pattern = 'repeat'; - /** @var Match[] An array of matches for the repeated section itself. */ + /** @var MatchInterface[] An array of matches for the repeated section itself. */ public $baseMatches = []; /** @var int The number of guesses required for the repeated section itself. */ @@ -70,9 +70,9 @@ public static function match($password, array $userInputs = []) $match[0]['token'], [ 'repeated_char' => $repeatedChar, - 'base_guesses' => $baseGuesses, - 'base_matches' => $baseMatches, - 'repeat_count' => $repeatCount + 'base_guesses' => $baseGuesses, + 'base_matches' => $baseMatches, + 'repeat_count' => $repeatCount, ] ); @@ -89,10 +89,10 @@ public function getFeedback($isSoleMatch) : 'Repeats like "abcabcabc" are only slightly harder to guess than "abc"'; return [ - 'warning' => $warning, + 'warning' => $warning, 'suggestions' => [ - 'Avoid repeated words and characters' - ] + 'Avoid repeated words and characters', + ], ]; } diff --git a/src/Matchers/SequenceMatch.php b/src/Matchers/SequenceMatch.php index 548275f..3a55ede 100644 --- a/src/Matchers/SequenceMatch.php +++ b/src/Matchers/SequenceMatch.php @@ -2,7 +2,7 @@ namespace ZxcvbnPhp\Matchers; -class SequenceMatch extends Match +class SequenceMatch extends BaseMatch { public const MAX_DELTA = 5; diff --git a/src/Matchers/SpatialMatch.php b/src/Matchers/SpatialMatch.php index 0f00828..b181c96 100644 --- a/src/Matchers/SpatialMatch.php +++ b/src/Matchers/SpatialMatch.php @@ -4,7 +4,7 @@ use ZxcvbnPhp\Matcher; -class SpatialMatch extends Match +class SpatialMatch extends BaseMatch { public const SHIFTED_CHARACTERS = '~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<>?'; diff --git a/src/Matchers/YearMatch.php b/src/Matchers/YearMatch.php index 433f2ea..dfd8c73 100644 --- a/src/Matchers/YearMatch.php +++ b/src/Matchers/YearMatch.php @@ -4,7 +4,7 @@ use ZxcvbnPhp\Matcher; -class YearMatch extends Match +class YearMatch extends BaseMatch { public const NUM_YEARS = 119; diff --git a/src/Scorer.php b/src/Scorer.php index 49ec1ee..6b79ef8 100644 --- a/src/Scorer.php +++ b/src/Scorer.php @@ -3,7 +3,8 @@ namespace ZxcvbnPhp; use ZxcvbnPhp\Matchers\Bruteforce; -use ZxcvbnPhp\Matchers\Match; +use ZxcvbnPhp\Matchers\BaseMatch; +use ZxcvbnPhp\Matchers\MatchInterface; /** * scorer - takes a list of potential matches, ranks and evaluates them, @@ -54,7 +55,7 @@ class Scorer * D^(l-1) approximates Sum(D^i for i in [1..l-1] * * @param string $password - * @param Match[] $matches + * @param MatchInterface[] $matches * @param bool $excludeAdditive * @return array Returns an array with these keys: [password, guesses, guesses_log10, sequence] */ @@ -75,8 +76,8 @@ public function getMostGuessableMatchSequence($password, $matches, $excludeAddit // small detail: for deterministic output, sort each sublist by i. foreach ($matchesByEndIndex as &$matches) { usort($matches, function ($a, $b) { - /** @var $a Match */ - /** @var $b Match */ + /** @var $a BaseMatch */ + /** @var $b BaseMatch */ return $a->begin - $b->begin; }); } @@ -97,7 +98,7 @@ public function getMostGuessableMatchSequence($password, $matches, $excludeAddit ]; for ($k = 0; $k < $length; $k++) { - /** @var Match $match */ + /** @var BaseMatch $match */ foreach ($matchesByEndIndex[$k] as $match) { if ($match->begin > 0) { foreach ($this->optimal['m'][$match->begin - 1] as $l => $null) { @@ -132,7 +133,7 @@ public function getMostGuessableMatchSequence($password, $matches, $excludeAddit /** * helper: considers whether a length-l sequence ending at match m is better (fewer guesses) * than previously encountered sequences, updating state if so. - * @param Match $match + * @param BaseMatch $match * @param int $length */ protected function update($match, $length) @@ -224,7 +225,7 @@ protected function makeBruteforceMatch($begin, $end) /** * helper: step backwards through optimal.m starting at the end, constructing the final optimal match sequence. * @param int $n - * @return Match[] array + * @return MatchInterface[] */ protected function unwind($n) { diff --git a/test/Matchers/L33tTest.php b/test/Matchers/L33tTest.php index 4178587..6f76d11 100644 --- a/test/Matchers/L33tTest.php +++ b/test/Matchers/L33tTest.php @@ -4,7 +4,7 @@ use ReflectionClass; use ZxcvbnPhp\Matchers\L33tMatch; -use ZxcvbnPhp\Matchers\Match; +use ZxcvbnPhp\Matchers\BaseMatch; class L33tTest extends AbstractMatchTest { @@ -295,9 +295,9 @@ public function variationsProvider() [ 'a8cet', 2, ['8' => 'b'] ], [ 'abce+', 2, ['+' => 't'] ], [ '48cet', 4, ['4' => 'a', '8' => 'b'] ], - [ 'a4a4aa', Match::binom(6, 2) + Match::binom(6, 1), ['4' => 'a'] ], - [ '4a4a44', Match::binom(6, 2) + Match::binom(6, 1), ['4' => 'a'] ], - [ 'a44att+', (Match::binom(4, 2) + Match::binom(4, 1)) * Match::binom(3, 1), ['4' => 'a', '+' => 't'] ] + ['a4a4aa', BaseMatch::binom(6, 2) + BaseMatch::binom(6, 1), ['4' => 'a'] ], + ['4a4a44', BaseMatch::binom(6, 2) + BaseMatch::binom(6, 1), ['4' => 'a'] ], + ['a44att+', (BaseMatch::binom(4, 2) + BaseMatch::binom(4, 1)) * BaseMatch::binom(3, 1), ['4' => 'a', '+' => 't'] ] ); } @@ -325,7 +325,7 @@ public function testCapitalisationNotAffectingL33t() { $token = 'Aa44aA'; $match = new L33tMatch($token, 0, strlen($token) - 1, $token, ['rank' => 1, 'sub' => ['4' => 'a']]); - $expected = Match::binom(6, 2) + Match::binom(6, 1); + $expected = BaseMatch::binom(6, 2) + BaseMatch::binom(6, 1); $class = new ReflectionClass('\\ZxcvbnPhp\\Matchers\\L33tMatch'); $method = $class->getMethod('getL33tVariations'); diff --git a/test/Matchers/MatchTest.php b/test/Matchers/MatchTest.php index 9e7c946..58cff6d 100644 --- a/test/Matchers/MatchTest.php +++ b/test/Matchers/MatchTest.php @@ -2,9 +2,10 @@ namespace ZxcvbnPhp\Test\Matchers; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ZxcvbnPhp\Matchers\DateMatch; -use ZxcvbnPhp\Matchers\Match; +use ZxcvbnPhp\Matchers\BaseMatch; use ZxcvbnPhp\Matchers\MatchInterface; class MatchTest extends TestCase @@ -31,7 +32,7 @@ public function binomialDataProvider() */ public function testBinomialCoefficient($n, $k, $expected) { - $this->assertEquals($expected, Match::binom($n, $k), "binom returns expected result"); + $this->assertEquals($expected, BaseMatch::binom($n, $k), "binom returns expected result"); } public function testBinomialMirrorIdentity() @@ -40,8 +41,8 @@ public function testBinomialMirrorIdentity() $k = 12; $this->assertEquals( - Match::binom($n, $k), - Match::binom($n, $n - $k), + BaseMatch::binom($n, $k), + BaseMatch::binom($n, $n - $k), "mirror identity" ); } @@ -52,8 +53,8 @@ public function testBinomialPascalsTriangleIdentity() $k = 12; $this->assertEquals( - Match::binom($n, $k), - Match::binom($n - 1, $k - 1) + Match::binom($n - 1, $k), + BaseMatch::binom($n, $k), + BaseMatch::binom($n - 1, $k - 1) + BaseMatch::binom($n - 1, $k), "pascal's triangle identity" ); } diff --git a/test/Matchers/MockMatch.php b/test/Matchers/MockMatch.php index 539053f..3c69244 100644 --- a/test/Matchers/MockMatch.php +++ b/test/Matchers/MockMatch.php @@ -2,9 +2,9 @@ namespace ZxcvbnPhp\Test\Matchers; -use ZxcvbnPhp\Matchers\Match; +use ZxcvbnPhp\Matchers\BaseMatch; -class MockMatch extends Match +class MockMatch extends BaseMatch { protected $guesses; diff --git a/test/Matchers/SpatialTest.php b/test/Matchers/SpatialTest.php index 281eb7b..419b29c 100644 --- a/test/Matchers/SpatialTest.php +++ b/test/Matchers/SpatialTest.php @@ -2,7 +2,7 @@ namespace ZxcvbnPhp\Test\Matchers; -use ZxcvbnPhp\Matchers\Match; +use ZxcvbnPhp\Matchers\BaseMatch; use ZxcvbnPhp\Matchers\SpatialMatch; /** @@ -162,7 +162,7 @@ public function testGuessesShifted() ]); $this->assertEquals( - $this->getBaseGuessCount($token) * (Match::binom(6, 2) + Match::binom(6, 1)), + $this->getBaseGuessCount($token) * (BaseMatch::binom(6, 2) + BaseMatch::binom(6, 1)), $match->getGuesses(), "guesses is added for shifted keys, similar to capitals in dictionary matching" ); diff --git a/test/ZxcvbnTest.php b/test/ZxcvbnTest.php index f3bd157..1f23f8f 100644 --- a/test/ZxcvbnTest.php +++ b/test/ZxcvbnTest.php @@ -5,7 +5,7 @@ use PHPUnit\Framework\TestCase; use ZxcvbnPhp\Matchers\Bruteforce; use ZxcvbnPhp\Matchers\DictionaryMatch; -use ZxcvbnPhp\Matchers\Match; +use ZxcvbnPhp\Matchers\MatchInterface; use ZxcvbnPhp\Zxcvbn; class ZxcvbnTest extends TestCase @@ -20,7 +20,7 @@ public function setUp(): void public function testMinimumGuessesForMultipleMatches() { - /** @var Match[] $matches */ + /** @var MatchInterface[] $matches */ $matches = $this->zxcvbn->passwordStrength('rockyou')['sequence']; // zxcvbn will return two matches: 'rock' (rank 359) and 'you' (rank 1). @@ -69,17 +69,17 @@ public function testZxcvbnReturnTypes($key, $type) public function sanityCheckDataProvider() { return [ - ['password', 0, ['dictionary', ], 'less than a second', 3], - ['65432', 0, ['sequence', ], 'less than a second', 101], - ['sdfgsdfg', 1, ['repeat', ], 'less than a second', 2595.0000000276], - ['fortitude', 1, ['dictionary', ], '1 second', 11308], - ['dfjkym', 1, ['bruteforce', ], '2 minutes', 1000001], - ['fortitude22', 2, ['dictionary', 'repeat', ], '2 minutes', 1140700], - ['absoluteadnap', 2, ['dictionary', 'dictionary', ], '25 minutes', 15187504], - ['knifeandspoon', 3, ['dictionary', 'dictionary', 'dictionary'], '1 day', 1108057600], - ['h1dden_26191', 3, ['dictionary', 'bruteforce', 'date' ], '3 days', 2642940400], - ['4rfv1236yhn!', 4, ['spatial', 'sequence', 'bruteforce'], '1 month', 38980000000.414], - ['BVidSNqe3oXVyE1996', 4, ['bruteforce', 'regex', ], 'centuries', 10000000000010000], + ['password', 0, ['dictionary',], 'less than a second', 3], + ['65432', 0, ['sequence',], 'less than a second', 101], + ['sdfgsdfg', 1, ['repeat',], 'less than a second', 2595.0000000276], + ['fortitude', 1, ['dictionary',], '1 second', 11308], + ['dfjkym', 1, ['bruteforce',], '2 minutes', 1000001], + ['fortitude22', 2, ['dictionary', 'repeat',], '2 minutes', 1140700], + ['absoluteadnap', 2, ['dictionary', 'dictionary',], '25 minutes', 15187504], + ['knifeandspoon', 3, ['dictionary', 'dictionary', 'dictionary'], '1 day', 1108057600], + ['h1dden_26191', 3, ['dictionary', 'bruteforce', 'date'], '3 days', 2642940400], + ['4rfv1236yhn!', 4, ['spatial', 'sequence', 'bruteforce'], '1 month', 38980000000.414], + ['BVidSNqe3oXVyE1996', 4, ['bruteforce', 'regex',], 'centuries', 10000000000010000], ]; }