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