Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
96.08% |
49 / 51 |
|
75.00% |
6 / 8 |
CRAP | |
0.00% |
0 / 1 |
| IsPasswordValueObject | |
96.08% |
49 / 51 |
|
75.00% |
6 / 8 |
15 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getRegularExpression | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
| createRegexPart | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
| getLowercaseRegularExpression | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getUppercaseRegularExpression | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getDigitRegularExpression | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getSpecialCharactersRegularExpression | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
2.01 | |||
| getMinLength | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getMaxLength | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getAllowedSpecialCharacters | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getMinSpecialCharacters | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getMinDigits | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getMinLowercase | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getMinUppercase | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| createRandom | |
96.30% |
26 / 27 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | namespace Apie\Core\ValueObjects; |
| 3 | |
| 4 | use Apie\Core\Attributes\CmsSingleInput; |
| 5 | use Apie\Core\Attributes\CmsValidationCheck; |
| 6 | use Apie\Core\Attributes\Description; |
| 7 | use Apie\Core\Randomizer\RandomizerInterface; |
| 8 | use Apie\Core\Randomizer\SecureRandomizer; |
| 9 | use SensitiveParameter; |
| 10 | use Stringable; |
| 11 | |
| 12 | #[Description('Represents a password. The password has certain restrictions like a minimum amount of alpha-numeric, digits and special characters')] |
| 13 | #[CmsSingleInput(['password'])] |
| 14 | #[CmsValidationCheck(message: 'apie.validation_errors.length', minLengthMethod: 'getMinLength', maxLengthMethod: 'getMaxLength')] |
| 15 | #[CmsValidationCheck(message: 'apie.validation_errors.password.lower_case', patternMethod: 'getLowercaseRegularExpression')] |
| 16 | #[CmsValidationCheck(message: 'apie.validation_errors.password.upper_case', patternMethod: 'getUppercaseRegularExpression')] |
| 17 | #[CmsValidationCheck(message: 'apie.validation_errors.password.digit', patternMethod: 'getDigitRegularExpression')] |
| 18 | #[CmsValidationCheck(message: 'apie.validation_errors.password.special', patternMethod: 'getSpecialCharactersRegularExpression')] |
| 19 | trait IsPasswordValueObject |
| 20 | { |
| 21 | use IsStringWithRegexValueObject { |
| 22 | __construct as private initObject; |
| 23 | } |
| 24 | |
| 25 | public function __construct(#[SensitiveParameter] string|int|float|bool|Stringable $input) |
| 26 | { |
| 27 | $this->initObject($input); |
| 28 | } |
| 29 | |
| 30 | public static function getRegularExpression(): string |
| 31 | { |
| 32 | $lowercase = self::createRegexPart('[a-z]', self::getMinLowercase()); |
| 33 | $uppercase = self::createRegexPart('[A-Z]', self::getMinUppercase()); |
| 34 | $digits = self::createRegexPart('[0-9]', self::getMinDigits()); |
| 35 | $specialCharactersRegex = str_replace('\#', '#', preg_quote(self::getAllowedSpecialCharacters(), '/')); |
| 36 | $specialCharacter = self::createRegexPart('[' . $specialCharactersRegex . ']', self::getMinSpecialCharacters()); |
| 37 | |
| 38 | $totalSize = '[a-zA-Z0-9' . $specialCharactersRegex . ']{' . self::getMinLength() . ',' . self::getMaxLength() . '}'; |
| 39 | return '/^' . $lowercase . $uppercase . $digits . $specialCharacter . $totalSize . '$/'; |
| 40 | } |
| 41 | |
| 42 | private static function createRegexPart(string $expression, ?int $minCount = null): string |
| 43 | { |
| 44 | return '(?=(.*' |
| 45 | . $expression |
| 46 | . '){' |
| 47 | . ($minCount === null ? '' : $minCount) |
| 48 | . ',})'; |
| 49 | } |
| 50 | |
| 51 | public static function getLowercaseRegularExpression(): string |
| 52 | { |
| 53 | return '/^' . self::createRegexPart('[a-z]', self::getMinLowercase()) . '.*$/'; |
| 54 | } |
| 55 | |
| 56 | public static function getUppercaseRegularExpression(): string |
| 57 | { |
| 58 | return '/^' . self::createRegexPart('[A-Z]', self::getMinUppercase()) . '.*$/'; |
| 59 | } |
| 60 | |
| 61 | public static function getDigitRegularExpression(): string |
| 62 | { |
| 63 | return '/^' . self::createRegexPart('[0-9]', self::getMinDigits()) . '.*$/'; |
| 64 | } |
| 65 | |
| 66 | public static function getSpecialCharactersRegularExpression(): ?string |
| 67 | { |
| 68 | $allowedSpecial = self::getAllowedSpecialCharacters(); |
| 69 | if (empty($allowedSpecial)) { |
| 70 | return null; |
| 71 | } |
| 72 | $specialCharactersRegex = str_replace('\#', '#', preg_quote($allowedSpecial, '/')); |
| 73 | return '/^' . self::createRegexPart( |
| 74 | '[' . $specialCharactersRegex . ']', |
| 75 | self::getMinSpecialCharacters() |
| 76 | ) . '.*$/'; |
| 77 | } |
| 78 | |
| 79 | abstract public static function getMinLength(): int; |
| 80 | |
| 81 | abstract public static function getMaxLength(): int; |
| 82 | |
| 83 | abstract public static function getAllowedSpecialCharacters(): string; |
| 84 | |
| 85 | abstract public static function getMinSpecialCharacters(): int; |
| 86 | |
| 87 | abstract public static function getMinDigits(): int; |
| 88 | |
| 89 | abstract public static function getMinLowercase(): int; |
| 90 | |
| 91 | abstract public static function getMinUppercase(): int; |
| 92 | |
| 93 | public static function createRandom(RandomizerInterface $generator = new SecureRandomizer()): static |
| 94 | { |
| 95 | $minLength = self::getMinLength(); |
| 96 | $maxLength = self::getMaxLength(); |
| 97 | $minSpecialCharacters = self::getMinSpecialCharacters(); |
| 98 | $minDigits = self::getMinDigits(); |
| 99 | $minLowercase = self::getMinLowercase(); |
| 100 | $minUppercase = self::getMinUppercase(); |
| 101 | $lowercaseCharacters = str_split('abcdefghijklmnopqrstuvwxyz'); |
| 102 | $uppercaseCharacters = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ'); |
| 103 | $specialCharacters = str_split(self::getAllowedSpecialCharacters()); |
| 104 | $generatedPassword = $generator->randomElements($specialCharacters, $minSpecialCharacters); |
| 105 | for ($i = 0; $i < $minDigits; $i++) { |
| 106 | $generatedPassword[] = $generator->randomDigit(); |
| 107 | } |
| 108 | for ($i = 0; $i < $minLowercase; $i++) { |
| 109 | $generatedPassword[] = $generator->randomElement($lowercaseCharacters); |
| 110 | } |
| 111 | for ($i = 0; $i < $minUppercase; $i++) { |
| 112 | $generatedPassword[] = $generator->randomElement($uppercaseCharacters); |
| 113 | } |
| 114 | $length = $generator->numberBetween($minLength, $maxLength); |
| 115 | for ($i = count($generatedPassword); $i < $length; $i++) { |
| 116 | $generatedPassword[] = $generator->randomElement([ |
| 117 | ...$lowercaseCharacters, |
| 118 | ...$uppercaseCharacters, |
| 119 | ...$specialCharacters |
| 120 | ]); |
| 121 | } |
| 122 | if (count($generatedPassword) > $maxLength) { |
| 123 | $generatedPassword = array_slice($generatedPassword, 0, $maxLength); |
| 124 | } |
| 125 | $generator->shuffle($generatedPassword); |
| 126 | |
| 127 | return self::fromNative(implode('', $generatedPassword)); |
| 128 | } |
| 129 | } |