Skip to content

Commit

Permalink
Date formatter in guessable data rule
Browse files Browse the repository at this point in the history
  • Loading branch information
Stadly committed Jan 22, 2019
1 parent b61519f commit 33eb605
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 80 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
- Date formatters for formatting dates. Date formatters can be chained with word formatters. The output from the date formatter is used as input for the next formatter in the chain.
- Date formatter combining the results from multiple date formatters.
- Default date formatter.
- Possible to specify date formatter for guessable data rules.

### Changed
- Nothing
- Constraint weight for guessable data rules is now argument number 4 instead of number 3. Date formatter is argument number 3.

### Fixed
- Nothing
Expand Down
98 changes: 20 additions & 78 deletions src/Rule/GuessableDataRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Stadly\PasswordPolice\Rule;

use DateTimeInterface;
use Stadly\PasswordPolice\DateFormatter;
use Stadly\PasswordPolice\DateFormatter\DefaultFormatter;
use Stadly\PasswordPolice\Password;
use Stadly\PasswordPolice\Policy;
use Stadly\PasswordPolice\Rule;
Expand All @@ -15,47 +17,6 @@

final class GuessableDataRule implements Rule
{
private const DATE_FORMATS = [
// Year
['Y'], // 2018

// Year month
['y', 'n'], // 18 8
['y', 'm'], // 18 08
['y', 'M'], // 18 Aug
['y', 'F'], // 18 August

// Month year
['n', 'y'], // 8 18
['M', 'y'], // Aug 18
['F', 'y'], // August 18

// Day month
['j', 'n'], // 4 8
['j', 'm'], // 4 08
['j', 'M'], // 4 Aug
['j', 'F'], // 4 August

// Month day
['n', 'j'], // 8 4
['n', 'd'], // 8 04
['M', 'j'], // Aug 4
['M', 'd'], // Aug 04
['F', 'j'], // August 4
['F', 'd'], // August 04
];

private const DATE_SEPARATORS = [
'',
'-',
' ',
'/',
'.',
',',
'. ',
', ',
];

/**
* @var (string|DateTimeInterface)[] Guessable data.
*/
Expand All @@ -66,6 +27,11 @@ final class GuessableDataRule implements Rule
*/
private $wordFormatter;

/**
* @var DateFormatter Date formatter.
*/
private $dateFormatter;

/**
* @var int Constraint weight.
*/
Expand All @@ -74,12 +40,18 @@ final class GuessableDataRule implements Rule
/**
* @param (string|DateTimeInterface)[] $guessableData Guessable data.
* @param WordFormatter[] $wordFormatters Word formatters.
* @param DateFormatter|null $dateFormatter Date formatter.
* @param int $weight Constraint weight.
*/
public function __construct(array $guessableData = [], array $wordFormatters = [], int $weight = 1)
{
public function __construct(
array $guessableData = [],
array $wordFormatters = [],
?DateFormatter $dateFormatter = null,
int $weight = 1
) {
$this->guessableData = $guessableData;
$this->wordFormatter = new FormatterCombiner($wordFormatters);
$this->dateFormatter = $dateFormatter ?? new DefaultFormatter();
$this->weight = $weight;
}

Expand Down Expand Up @@ -153,50 +125,20 @@ private function getGuessableData($password)
private function contains(string $password, $data): bool
{
if ($data instanceof DateTimeInterface) {
return $this->containsDate($password, $data);
$strings = $this->dateFormatter->apply([$data]);
} else {
$strings = [$data];
}

return $this->containsString($password, $data);
}

/**
* @param string $password Password to check.
* @param string $string String to check.
* @return bool Whether the password contains the string.
*/
private function containsString(string $password, string $string): bool
{
return mb_stripos($password, $string) !== false;
}

/**
* @param string $password Password to check.
* @param DateTimeInterface $date Date to check.
* @return bool Whether the password contains the date.
*/
private function containsDate(string $password, DateTimeInterface $date): bool
{
foreach ($this->getDateFormats() as $format) {
if ($this->containsString($password, $date->format($format))) {
foreach ($strings as $string) {
if (mb_stripos($password, $string) !== false) {
return true;
}
}

return false;
}

/**
* @return iterable<string> Date formats.
*/
private function getDateFormats(): iterable
{
foreach (self::DATE_FORMATS as $format) {
foreach (self::DATE_SEPARATORS as $separator) {
yield implode($separator, $format);
}
}
}

/**
* @param string|DateTimeInterface $data Data that violates the constraint.
* @return string Message explaining the violation.
Expand Down
23 changes: 22 additions & 1 deletion tests/Rule/GuessableDataRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use DateTime;
use PHPUnit\Framework\TestCase;
use Stadly\PasswordPolice\DateFormatter;
use Stadly\PasswordPolice\Password;
use Stadly\PasswordPolice\ValidationError;
use Stadly\PasswordPolice\WordFormatter;
Expand Down Expand Up @@ -111,7 +112,7 @@ public function testPasswordCanNotContainGuessableDate(): void
*/
public function testRuleIsSatisfiedWhenConstraintWeightIsLowerThanTestWeight(): void
{
$rule = new GuessableDataRule([], [], 1);
$rule = new GuessableDataRule([], [], null, 1);
$password = new Password('foobar', ['oba']);

self::assertTrue($rule->test($password, 2));
Expand Down Expand Up @@ -237,6 +238,26 @@ static function (iterable $words): Traversable {
self::assertFalse($rule->test(new Password('apple', ['fpple'])));
}

/**
* @covers ::test
*/
public function testDateFormatterCanBeCustomized(): void
{
$dateFormatter = $this->createMock(DateFormatter::class);
$dateFormatter->method('apply')->willReturnCallback(
static function (iterable $dates): Traversable {
foreach ($dates as $date) {
yield $date->format('m\o\n\t\h');
}
}
);

$rule = new GuessableDataRule([], [], $dateFormatter);

self::assertFalse($rule->test(new Password('test 11onth foo', [new DateTime('2018-11-28')])));
self::assertTrue($rule->test(new Password('2018-11-28', [new DateTime('2018-11-28')])));
}

/**
* @covers ::validate
*/
Expand Down

0 comments on commit 33eb605

Please sign in to comment.