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\Attributes\Description;
7use Apie\Core\Randomizer\RandomizerInterface;
8use Apie\Core\Randomizer\SecureRandomizer;
9use SensitiveParameter;
10use 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')]
19trait 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}