From 075f5ae813066bc24ea10747c27d942558ea9e49 Mon Sep 17 00:00:00 2001 From: Stadly Date: Fri, 14 Dec 2018 12:29:39 +0100 Subject: [PATCH] Symbol rule --- CHANGELOG.md | 3 +- src/Rule/CharacterClass.php | 59 +------ src/Rule/Symbol.php | 67 ++++++++ tests/Rule/CharacterClassTest.php | 94 +++-------- tests/Rule/SymbolTest.php | 250 ++++++++++++++++++++++++++++++ 5 files changed, 342 insertions(+), 131 deletions(-) create mode 100644 src/Rule/Symbol.php create mode 100644 tests/Rule/SymbolTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d8d75..c8089db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,11 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip ## [Unreleased](https://github.com/Stadly/PasswordPolice/compare/v0.9.0...HEAD) ### Added -- Nothing +- Rule enforcing the use of symbols in passwords. ### Changed - Improved exception messages. +- The character class rule can not be used directly anymore. Use the symbol rule instead. ### Fixed - Nothing diff --git a/src/Rule/CharacterClass.php b/src/Rule/CharacterClass.php index 322161c..b7e46f3 100644 --- a/src/Rule/CharacterClass.php +++ b/src/Rule/CharacterClass.php @@ -8,7 +8,7 @@ use Stadly\PasswordPolice\Password; use Stadly\PasswordPolice\Policy; -class CharacterClass implements RuleInterface +abstract class CharacterClass implements RuleInterface { /** * @var string Characters matched by the rule. @@ -133,61 +133,4 @@ private function getCount(string $password): int return $count; } - - /** - * {@inheritDoc} - */ - public function getMessage(): string - { - $translator = Policy::getTranslator(); - - if ($this->max === null) { - return $translator->trans( - 'There must be at least one character matching %characters%.|'. - 'There must be at least %count% characters matching %characters%.', - [ - '%count%' => $this->min, - '%characters%' => $this->characters, - ] - ); - } - - if ($this->max === 0) { - return $translator->trans( - 'There must be no characters matching %characters%.', - ['%characters%' => $this->characters] - ); - } - - if ($this->min === 0) { - return $translator->trans( - 'There must be at most one character matching %characters%.|'. - 'There must be at most %count% characters matching %characters%.', - [ - '%count%' => $this->max, - '%characters%' => $this->characters, - ] - ); - } - - if ($this->min === $this->max) { - return $translator->trans( - 'There must be exactly one character matching %characters%.|'. - 'There must be exactly %count% characters matching %characters%.', - [ - '%count%' => $this->min, - '%characters%' => $this->characters, - ] - ); - } - - return $translator->trans( - 'There must be between %min% and %max% characters matching %characters%.', - [ - '%min%' => $this->min, - '%max%' => $this->max, - '%characters%' => $this->characters, - ] - ); - } } diff --git a/src/Rule/Symbol.php b/src/Rule/Symbol.php new file mode 100644 index 0000000..cb81e1e --- /dev/null +++ b/src/Rule/Symbol.php @@ -0,0 +1,67 @@ +max === null) { + return $translator->trans( + 'There must be at least one symbol (%characters%).|'. + 'There must be at least %count% symbols (%characters%).', + [ + '%count%' => $this->min, + '%characters%' => $this->characters, + ] + ); + } + + if ($this->max === 0) { + return $translator->trans( + 'There must be no symbols (%characters%).', + ['%characters%' => $this->characters] + ); + } + + if ($this->min === 0) { + return $translator->trans( + 'There must be at most one symbol (%characters%).|'. + 'There must be at most %count% symbols (%characters%).', + [ + '%count%' => $this->max, + '%characters%' => $this->characters, + ] + ); + } + + if ($this->min === $this->max) { + return $translator->trans( + 'There must be exactly one symbol (%characters%).|'. + 'There must be exactly %count% symbols (%characters%).', + [ + '%count%' => $this->min, + '%characters%' => $this->characters, + ] + ); + } + + return $translator->trans( + 'There must be between %min% and %max% symbols (%characters%).', + [ + '%min%' => $this->min, + '%max%' => $this->max, + '%characters%' => $this->characters, + ] + ); + } +} diff --git a/tests/Rule/CharacterClassTest.php b/tests/Rule/CharacterClassTest.php index fe74d1f..87078eb 100644 --- a/tests/Rule/CharacterClassTest.php +++ b/tests/Rule/CharacterClassTest.php @@ -19,10 +19,10 @@ final class CharacterClassTest extends TestCase */ public function testCanConstructRuleWithMinConstraint(): void { - $rule = new CharacterClass('$%&@!', 5, null); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 5, null]); // Force generation of code coverage - $ruleConstruct = new CharacterClass('$%&@!', 5, null); + $ruleConstruct = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 5, null]); self::assertEquals($rule, $ruleConstruct); } @@ -31,10 +31,10 @@ public function testCanConstructRuleWithMinConstraint(): void */ public function testCanConstructRuleWithMaxConstraint(): void { - $rule = new CharacterClass('$%&@!', 0, 10); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 0, 10]); // Force generation of code coverage - $ruleConstruct = new CharacterClass('$%&@!', 0, 10); + $ruleConstruct = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 0, 10]); self::assertEquals($rule, $ruleConstruct); } @@ -43,10 +43,10 @@ public function testCanConstructRuleWithMaxConstraint(): void */ public function testCanConstructRuleWithBothMinAndMaxConstraint(): void { - $rule = new CharacterClass('$%&@!', 5, 10); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 5, 10]); // Force generation of code coverage - $ruleConstruct = new CharacterClass('$%&@!', 5, 10); + $ruleConstruct = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 5, 10]); self::assertEquals($rule, $ruleConstruct); } @@ -57,7 +57,7 @@ public function testCannotConstructRuleWithNegativeMinConstraint(): void { $this->expectException(InvalidArgumentException::class); - $rule = new CharacterClass('$%&@!', -10, null); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', -10, null]); } /** @@ -67,7 +67,7 @@ public function testCannotConstructRuleWithMaxConstraintSmallerThanMinConstraint { $this->expectException(InvalidArgumentException::class); - $rule = new CharacterClass('$%&@!', 10, 5); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 10, 5]); } /** @@ -77,7 +77,7 @@ public function testCannotConstructUnconstrainedRule(): void { $this->expectException(InvalidArgumentException::class); - $rule = new CharacterClass('$%&@!', 0, null); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 0, null]); } /** @@ -85,10 +85,10 @@ public function testCannotConstructUnconstrainedRule(): void */ public function testCanConstructRuleWithMinConstraintEqualToMaxConstraint(): void { - $rule = new CharacterClass('$%&@!', 5, 5); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 5, 5]); // Force generation of code coverage - $ruleConstruct = new CharacterClass('$%&@!', 5, 5); + $ruleConstruct = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 5, 5]); self::assertEquals($rule, $ruleConstruct); } @@ -99,7 +99,7 @@ public function testCannotConstructRuleWithNoCharacters(): void { $this->expectException(InvalidArgumentException::class); - $rule = new CharacterClass(''); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['']); } /** @@ -107,7 +107,7 @@ public function testCannotConstructRuleWithNoCharacters(): void */ public function testCanGetCharacters(): void { - $rule = new CharacterClass('$%&@!'); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!']); self::assertSame('$%&@!', $rule->getCharacters()); } @@ -117,7 +117,7 @@ public function testCanGetCharacters(): void */ public function testCanGetMinConstraint(): void { - $rule = new CharacterClass('$%&@!', 5, 10); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 5, 10]); self::assertSame(5, $rule->getMin()); } @@ -127,7 +127,7 @@ public function testCanGetMinConstraint(): void */ public function testCanGetMaxConstraint(): void { - $rule = new CharacterClass('$%&@!', 5, 10); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 5, 10]); self::assertSame(10, $rule->getMax()); } @@ -137,7 +137,7 @@ public function testCanGetMaxConstraint(): void */ public function testMinConstraintCanBeSatisfied(): void { - $rule = new CharacterClass('$%&@!', 2, null); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 2, null]); self::assertTrue($rule->test('FOO bar $$@')); } @@ -147,7 +147,7 @@ public function testMinConstraintCanBeSatisfied(): void */ public function testMinConstraintCanBeUnsatisfied(): void { - $rule = new CharacterClass('$%&@!', 2, null); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 2, null]); self::assertFalse($rule->test('FOO BAR $')); } @@ -157,7 +157,7 @@ public function testMinConstraintCanBeUnsatisfied(): void */ public function testMaxConstraintCanBeSatisfied(): void { - $rule = new CharacterClass('$%&@!', 0, 3); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 0, 3]); self::assertTrue($rule->test('FOO bar $$@')); } @@ -167,7 +167,7 @@ public function testMaxConstraintCanBeSatisfied(): void */ public function testMaxConstraintCanBeUnsatisfied(): void { - $rule = new CharacterClass('$%&@!', 0, 3); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 0, 3]); self::assertFalse($rule->test('foo bar $$@!')); } @@ -177,12 +177,12 @@ public function testMaxConstraintCanBeUnsatisfied(): void */ public function testEnforceDoesNotThrowExceptionWhenRuleIsSatisfied(): void { - $rule = new CharacterClass('$%&@!', 1, null); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 1, null]); $rule->enforce('&'); // Force generation of code coverage - $ruleConstruct = new CharacterClass('$%&@!', 1, null); + $ruleConstruct = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 1, null]); self::assertEquals($rule, $ruleConstruct); } @@ -191,60 +191,10 @@ public function testEnforceDoesNotThrowExceptionWhenRuleIsSatisfied(): void */ public function testEnforceThrowsExceptionWhenRuleIsNotSatisfied(): void { - $rule = new CharacterClass('$%&@!', 1, null); + $rule = $this->getMockForAbstractClass(CharacterClass::class, ['$%&@!', 1, null]); $this->expectException(RuleException::class); $rule->enforce('€'); } - - /** - * @covers ::getMessage - */ - public function testCanGetMessageForRuleWithMinConstraint(): void - { - $rule = new CharacterClass('$%&@!', 5, null); - - self::assertSame('There must be at least 5 characters matching $%&@!.', $rule->getMessage()); - } - - /** - * @covers ::getMessage - */ - public function testCanGetMessageForRuleWithMaxConstraint(): void - { - $rule = new CharacterClass('$%&@!', 0, 10); - - self::assertSame('There must be at most 10 characters matching $%&@!.', $rule->getMessage()); - } - - /** - * @covers ::getMessage - */ - public function testCanGetMessageForRuleWithBothMinAndMaxConstraint(): void - { - $rule = new CharacterClass('$%&@!', 5, 10); - - self::assertSame('There must be between 5 and 10 characters matching $%&@!.', $rule->getMessage()); - } - - /** - * @covers ::getMessage - */ - public function testCanGetMessageForRuleWithMaxConstraintEqualToZero(): void - { - $rule = new CharacterClass('$%&@!', 0, 0); - - self::assertSame('There must be no characters matching $%&@!.', $rule->getMessage()); - } - - /** - * @covers ::getMessage - */ - public function testCanGetMessageForRuleWithMinConstraintEqualToMaxConstraint(): void - { - $rule = new CharacterClass('$%&@!', 3, 3); - - self::assertSame('There must be exactly 3 characters matching $%&@!.', $rule->getMessage()); - } } diff --git a/tests/Rule/SymbolTest.php b/tests/Rule/SymbolTest.php new file mode 100644 index 0000000..11759c1 --- /dev/null +++ b/tests/Rule/SymbolTest.php @@ -0,0 +1,250 @@ + + * @covers :: + */ +final class SymbolTest extends TestCase +{ + /** + * @covers ::__construct + */ + public function testCanConstructRuleWithMinConstraint(): void + { + $rule = new Symbol('$%&@!', 5, null); + + // Force generation of code coverage + $ruleConstruct = new Symbol('$%&@!', 5, null); + self::assertEquals($rule, $ruleConstruct); + } + + /** + * @covers ::__construct + */ + public function testCanConstructRuleWithMaxConstraint(): void + { + $rule = new Symbol('$%&@!', 0, 10); + + // Force generation of code coverage + $ruleConstruct = new Symbol('$%&@!', 0, 10); + self::assertEquals($rule, $ruleConstruct); + } + + /** + * @covers ::__construct + */ + public function testCanConstructRuleWithBothMinAndMaxConstraint(): void + { + $rule = new Symbol('$%&@!', 5, 10); + + // Force generation of code coverage + $ruleConstruct = new Symbol('$%&@!', 5, 10); + self::assertEquals($rule, $ruleConstruct); + } + + /** + * @covers ::__construct + */ + public function testCannotConstructRuleWithNegativeMinConstraint(): void + { + $this->expectException(InvalidArgumentException::class); + + $rule = new Symbol('$%&@!', -10, null); + } + + /** + * @covers ::__construct + */ + public function testCannotConstructRuleWithMaxConstraintSmallerThanMinConstraint(): void + { + $this->expectException(InvalidArgumentException::class); + + $rule = new Symbol('$%&@!', 10, 5); + } + + /** + * @covers ::__construct + */ + public function testCannotConstructUnconstrainedRule(): void + { + $this->expectException(InvalidArgumentException::class); + + $rule = new Symbol('$%&@!', 0, null); + } + + /** + * @covers ::__construct + */ + public function testCanConstructRuleWithMinConstraintEqualToMaxConstraint(): void + { + $rule = new Symbol('$%&@!', 5, 5); + + // Force generation of code coverage + $ruleConstruct = new Symbol('$%&@!', 5, 5); + self::assertEquals($rule, $ruleConstruct); + } + + /** + * @covers ::__construct + */ + public function testCannotConstructRuleWithNoCharacters(): void + { + $this->expectException(InvalidArgumentException::class); + + $rule = new Symbol(''); + } + + /** + * @covers ::getCharacters + */ + public function testCanGetCharacters(): void + { + $rule = new Symbol('$%&@!'); + + self::assertSame('$%&@!', $rule->getCharacters()); + } + + /** + * @covers ::getMin + */ + public function testCanGetMinConstraint(): void + { + $rule = new Symbol('$%&@!', 5, 10); + + self::assertSame(5, $rule->getMin()); + } + + /** + * @covers ::getMax + */ + public function testCanGetMaxConstraint(): void + { + $rule = new Symbol('$%&@!', 5, 10); + + self::assertSame(10, $rule->getMax()); + } + + /** + * @covers ::test + */ + public function testMinConstraintCanBeSatisfied(): void + { + $rule = new Symbol('$%&@!', 2, null); + + self::assertTrue($rule->test('FOO bar $$@')); + } + + /** + * @covers ::test + */ + public function testMinConstraintCanBeUnsatisfied(): void + { + $rule = new Symbol('$%&@!', 2, null); + + self::assertFalse($rule->test('FOO BAR $')); + } + + /** + * @covers ::test + */ + public function testMaxConstraintCanBeSatisfied(): void + { + $rule = new Symbol('$%&@!', 0, 3); + + self::assertTrue($rule->test('FOO bar $$@')); + } + + /** + * @covers ::test + */ + public function testMaxConstraintCanBeUnsatisfied(): void + { + $rule = new Symbol('$%&@!', 0, 3); + + self::assertFalse($rule->test('foo bar $$@!')); + } + + /** + * @covers ::enforce + */ + public function testEnforceDoesNotThrowExceptionWhenRuleIsSatisfied(): void + { + $rule = new Symbol('$%&@!', 1, null); + + $rule->enforce('&'); + + // Force generation of code coverage + $ruleConstruct = new Symbol('$%&@!', 1, null); + self::assertEquals($rule, $ruleConstruct); + } + + /** + * @covers ::enforce + */ + public function testEnforceThrowsExceptionWhenRuleIsNotSatisfied(): void + { + $rule = new Symbol('$%&@!', 1, null); + + $this->expectException(RuleException::class); + + $rule->enforce('€'); + } + + /** + * @covers ::getMessage + */ + public function testCanGetMessageForRuleWithMinConstraint(): void + { + $rule = new Symbol('$%&@!', 5, null); + + self::assertSame('There must be at least 5 symbols ($%&@!).', $rule->getMessage()); + } + + /** + * @covers ::getMessage + */ + public function testCanGetMessageForRuleWithMaxConstraint(): void + { + $rule = new Symbol('$%&@!', 0, 10); + + self::assertSame('There must be at most 10 symbols ($%&@!).', $rule->getMessage()); + } + + /** + * @covers ::getMessage + */ + public function testCanGetMessageForRuleWithBothMinAndMaxConstraint(): void + { + $rule = new Symbol('$%&@!', 5, 10); + + self::assertSame('There must be between 5 and 10 symbols ($%&@!).', $rule->getMessage()); + } + + /** + * @covers ::getMessage + */ + public function testCanGetMessageForRuleWithMaxConstraintEqualToZero(): void + { + $rule = new Symbol('$%&@!', 0, 0); + + self::assertSame('There must be no symbols ($%&@!).', $rule->getMessage()); + } + + /** + * @covers ::getMessage + */ + public function testCanGetMessageForRuleWithMinConstraintEqualToMaxConstraint(): void + { + $rule = new Symbol('$%&@!', 3, 3); + + self::assertSame('There must be exactly 3 symbols ($%&@!).', $rule->getMessage()); + } +}