Validator   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 39
dl 0
loc 168
rs 10
c 3
b 1
f 0
wmc 25

12 Methods

Rating   Name   Duplication   Size   Complexity  
A hasSequentialCharacters() 0 5 3
A isValidPassword() 0 5 3
A meetsComplexityRequirements() 0 5 3
A hasSequentialLetters() 0 3 1
A meetsLengthRequirement() 0 5 2
A __construct() 0 3 1
A countCharacterTypes() 0 18 5
A hasKeyboardPattern() 0 3 1
A meetsCharacterTypeRequirements() 0 3 1
A hasCommonPatterns() 0 19 3
A hasSequentialNumbers() 0 3 1
A hasRepeatedCharacters() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PasswordHelper;
6
7
/**
8
 * Validates passwords against a set of policy requirements.
9
 *
10
 * This class provides methods to check if passwords meet various security
11
 * requirements such as minimum length, character types, and complexity rules.
12
 *
13
 * @package PasswordHelper
14
 */
15
class Validator
16
{
17
    /**
18
     * Creates a new password validator.
19
     */
20
    public function __construct(
21
        private Policy $policy
22
    ) {
23
    }
24
25
    /**
26
     * Validates a password against the policy requirements.
27
     *
28
     * @param string $password The password to validate
29
     * @return bool True if the password meets all requirements
30
     */
31
    public function isValidPassword(string $password): bool
32
    {
33
        return $this->meetsLengthRequirement($password) &&
34
               $this->meetsCharacterTypeRequirements($password) &&
35
               $this->meetsComplexityRequirements($password);
36
    }
37
38
    /**
39
     * Checks if the password meets the length requirements.
40
     *
41
     * @param string $password The password to check
42
     * @return bool True if the length requirements are met
43
     */
44
    private function meetsLengthRequirement(string $password): bool
45
    {
46
        $length = strlen($password);
47
        return $length >= $this->policy->getMinimumLength() &&
48
               $length <= $this->policy->getMaximumLength();
49
    }
50
51
    /**
52
     * Checks if the password meets the character type requirements.
53
     *
54
     * @param string $password The password to check
55
     * @return bool True if the character type requirements are met
56
     */
57
    private function meetsCharacterTypeRequirements(string $password): bool
58
    {
59
        return $this->countCharacterTypes($password) >= $this->policy->getMinimumCharacterTypes();
60
    }
61
62
    /**
63
     * Counts the number of different character types in the password.
64
     *
65
     * @param string $password The password to check
66
     * @return int The number of different character types
67
     */
68
    private function countCharacterTypes(string $password): int
69
    {
70
        $types = 0;
71
        
72
        if (preg_match('/[A-Z]/', $password)) {
73
            $types++;
74
        }
75
        if (preg_match('/[a-z]/', $password)) {
76
            $types++;
77
        }
78
        if (preg_match('/\d/', $password)) {
79
            $types++;
80
        }
81
        if (preg_match('/[^a-zA-Z\d]/', $password)) {
82
            $types++;
83
        }
84
        
85
        return $types;
86
    }
87
88
    /**
89
     * Checks if the password meets the complexity requirements.
90
     *
91
     * @param string $password The password to check
92
     * @return bool True if the complexity requirements are met
93
     */
94
    private function meetsComplexityRequirements(string $password): bool
95
    {
96
        return !$this->hasRepeatedCharacters($password) &&
97
               !$this->hasSequentialCharacters($password) &&
98
               !$this->hasCommonPatterns($password);
99
    }
100
101
    /**
102
     * Checks if the password contains repeated characters.
103
     *
104
     * @param string $password The password to check
105
     * @return bool True if repeated characters are found
106
     */
107
    private function hasRepeatedCharacters(string $password): bool
108
    {
109
        return (bool) preg_match('/(.)\1{2,}/', $password);
110
    }
111
112
    /**
113
     * Checks if the password contains sequential characters.
114
     *
115
     * @param string $password The password to check
116
     * @return bool True if sequential characters are found
117
     */
118
    private function hasSequentialCharacters(string $password): bool
119
    {
120
        return $this->hasSequentialNumbers($password) ||
121
               $this->hasSequentialLetters($password) ||
122
               $this->hasKeyboardPattern($password);
123
    }
124
125
    /**
126
     * Checks if the password contains sequential numbers.
127
     *
128
     * @param string $password The password to check
129
     * @return bool True if sequential numbers are found
130
     */
131
    private function hasSequentialNumbers(string $password): bool
132
    {
133
        return (bool) preg_match('/012|123|234|345|456|567|678|789/', $password);
134
    }
135
136
    /**
137
     * Checks if the password contains sequential letters.
138
     *
139
     * @param string $password The password to check
140
     * @return bool True if sequential letters are found
141
     */
142
    private function hasSequentialLetters(string $password): bool
143
    {
144
        return (bool) preg_match('/abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz/i', $password);
145
    }
146
147
    /**
148
     * Checks if the password contains keyboard patterns.
149
     *
150
     * @param string $password The password to check
151
     * @return bool True if keyboard patterns are found
152
     */
153
    private function hasKeyboardPattern(string $password): bool
154
    {
155
        return (bool) preg_match('/qwer|wert|erty|rtyu|tyui|yuio|uiop|asdf|sdfg|dfgh|fghj|ghjk|hjkl|zxcv|xcvb|cvbn|vbnm/i', $password);
156
    }
157
158
    /**
159
     * Checks if the password contains common patterns.
160
     *
161
     * @param string $password The password to check
162
     * @return bool True if common patterns are found
163
     */
164
    private function hasCommonPatterns(string $password): bool
165
    {
166
        $commonPatterns = [
167
            '123456', 'password', 'qwerty', 'admin', 'welcome',
168
            'monkey', 'letmein', 'dragon', 'baseball', 'iloveyou',
169
            'trustno1', 'sunshine', 'master', 'hello', 'shadow',
170
            'ashley', 'football', 'jesus', 'michael', 'ninja',
171
            'mustang', 'password1', '12345678', 'qwerty123', 'admin123'
172
        ];
173
174
        $password = strtolower($password);
175
        
176
        foreach ($commonPatterns as $pattern) {
177
            if (str_contains($password, $pattern)) {
178
                return true;
179
            }
180
        }
181
        
182
        return false;
183
    }
184
}
185