Issues (4)

lib/Support/Validator.php (1 issue)

1
<?php
2
3
namespace Ballen\Plexity\Support;
4
5
use Ballen\Plexity\Interfaces\PasswordHistoryInterface;
6
use Ballen\Plexity\Plexity;
7
use Ballen\Plexity\Exceptions\ValidationException;
8
9
/**
10
 * Plexity
11
 *
12
 * Plexity (Password Complexity) is a password complexity library that
13
 * enables you to set "rules" for a password (or any other kind of string) that
14
 * you can then check against in your application.
15
 *
16
 * @author Bobby Allen <[email protected]>
17
 * @license http://opensource.org/licenses/MIT
18
 * @link https://github.com/allebb/plexity
19
 * @link https://bobbyallen.me
20
 *
21
 */
22
class Validator
23
{
24
25
    /**
26
     * RegEx for uppercase character detection.
27
     */
28
    const REGEX_UPPER_CASE = "/[A-Z]/";
29
30
    /**
31
     * RegEx for lowercase character detection.
32
     */
33
    const REGEX_LOWER_CASE = "/[a-z]/";
34
35
    /**
36
     * The Plexity object (contains the validation configuration)
37
     * @var Plexity
38
     */
39
    private $configuration;
40
41
    /**
42
     * Numeric values list
43
     * @var array<int>
44
     */
45
    protected $numbers = [
46
        1,
47
        2,
48
        3,
49
        4,
50
        5,
51
        6,
52
        7,
53
        8,
54
        9,
55
        0
56
    ];
57
58
    /**
59
     * Special Character list
60
     * @see https://www.owasp.org/index.php/Password_special_characters
61
     * @var array<string>
62
     */
63
    protected $specialCharacters = [
64
        ' ',
65
        '!',
66
        '"',
67
        '#',
68
        '$',
69
        '%',
70
        '&',
71
        '\'',
72
        '(',
73
        ')',
74
        '*',
75
        '+',
76
        ',',
77
        '.',
78
        '/',
79
        ':',
80
        ';',
81
        '<',
82
        '=',
83
        '>',
84
        '?',
85
        '@',
86
        '[',
87
        ']',
88
        '\\',
89
        '^',
90
        '_',
91
        '-',
92
        '`',
93
        '{',
94
        '|',
95
        '}',
96
        '~',
97
    ];
98
99
    /**
100
     * Validates all the configured rules and responds as requested.
101
     * @return boolean
102
     * @throws ValidationException
103
     */
104 33
    public function validate(Plexity $configuration)
105
    {
106 33
        $this->configuration = $configuration;
107 33
        $this->checkMinimumLength();
108 31
        $this->checkMaximumLength();
109 29
        $this->checkLowerCase();
110 27
        $this->checkUpperCase();
111 25
        $this->checkNumericCharacters();
112 22
        $this->checkSpecialCharacters();
113 20
        $this->checkNotIn();
114 18
        $this->checkNotHaving();
115 17
        return true;
116
    }
117
118
    /**
119
     * Checks the minimum length requirement.
120
     * @return void
121
     * @throws ValidationException
122
     */
123 33
    public function checkMinimumLength()
124
    {
125 33
        if ($this->configuration->rules()->get(Plexity::RULE_LENGTH_MIN) > 0) {
126 6
            if (!$this->validateLengthMin()) {
127 2
                throw new ValidationException('The length does not meet the minimum length requirements.');
128
            }
129
        }
130
    }
131
132
    /**
133
     * Checks the minimum maximum length requirement.
134
     * @return void
135
     * @throws ValidationException
136
     */
137 31
    public function checkMaximumLength()
138
    {
139 31
        if ($this->configuration->rules()->get(Plexity::RULE_LENGTH_MAX) > 0) {
140 5
            if (!$this->validateLengthMax()) {
141 2
                throw new ValidationException('The length exceeds the maximum length requirements.');
142
            }
143
        }
144
    }
145
146
    /**
147
     * Checks the lowercase character(s) requirement.
148
     * @return void
149
     * @throws ValidationException
150
     */
151 29
    public function checkLowerCase()
152
    {
153 29
        if ($this->configuration->rules()->get(Plexity::RULE_LOWER) > 0) {
154 4
            if (!$this->validateLowerCase()) {
155 2
                throw new ValidationException('The string failed to meet the lower case requirements.');
156
            }
157
        }
158
    }
159
160
    /**
161
     * Checks the upper case character(s) requirement.
162
     * @return void
163
     * @throws ValidationException
164
     */
165 27
    public function checkUpperCase()
166
    {
167 27
        if ($this->configuration->rules()->get(Plexity::RULE_UPPER) > 0) {
168 4
            if (!$this->validateUpperCase()) {
169 2
                throw new ValidationException('The string failed to meet the upper case requirements.');
170
            }
171
        }
172
    }
173
174
    /**
175
     * Checks the numeric character(s) requirement.
176
     * @return void
177
     * @throws ValidationException
178
     */
179 25
    public function checkNumericCharacters()
180
    {
181 25
        if ($this->configuration->rules()->get(Plexity::RULE_NUMERIC) > 0) {
182 6
            if (!$this->validateNumericCharacters()) {
183 3
                throw new ValidationException('The string failed to meet the numeric character requirements.');
184
            }
185
        }
186
    }
187
188
    /**
189
     * Checks the special character(s) requirement.
190
     * @return void
191
     * @throws ValidationException
192
     */
193 22
    public function checkSpecialCharacters()
194
    {
195 22
        if ($this->configuration->rules()->get(Plexity::RULE_SPECIAL) > 0) {
196 5
            if (!$this->validateSpecialCharacters()) {
197 2
                throw new ValidationException('The string failed to meet the special character requirements.');
198
            }
199
        }
200
    }
201
202
    /**
203
     * Validates if a string is not in a array (password history database).
204
     * @return void
205
     * @throws ValidationException
206
     */
207 20
    public function checkNotIn()
208
    {
209
210 20
        if ($this->configuration->rules()->get(Plexity::RULE_NOT_IN) === null) {
211 16
            return;
212
        }
213
214 4
        if ($this->configuration->rules()->get(Plexity::RULE_NOT_IN) instanceof PasswordHistoryInterface) {
215 2
            $this->validateNotInPasswordHistoryImplementation();
216
        }
217
218 3
        if (is_array($this->configuration->rules()->get(Plexity::RULE_NOT_IN)) && count($this->configuration->rules()->get(Plexity::RULE_NOT_IN)) > 0) {
219 2
            $this->validateNotInArray();
220
        }
221
222
    }
223
224
    /**
225
     * Validates if a string does not contain any words from the provided array.
226
     * @return void
227
     * @throws ValidationException
228
     */
229 18
    public function checkNotHaving()
230
    {
231
232 18
        if ($this->configuration->rules()->get(Plexity::RULE_NOT_HAVING) === null) {
233 16
            return;
234
        }
235
236 2
        if (is_array($this->configuration->rules()->get(Plexity::RULE_NOT_HAVING)) && count($this->configuration->rules()->get(Plexity::RULE_NOT_HAVING)) > 0) {
0 ignored issues
show
It seems like $this->configuration->ru...exity::RULE_NOT_HAVING) can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

236
        if (is_array($this->configuration->rules()->get(Plexity::RULE_NOT_HAVING)) && count(/** @scrutinizer ignore-type */ $this->configuration->rules()->get(Plexity::RULE_NOT_HAVING)) > 0) {
Loading history...
237 2
            $this->validateNotHaving();
238
        }
239
240
    }
241
242
    /**
243
     * Validates the upper case requirements.
244
     * @return boolean
245
     */
246 4
    private function validateUpperCase()
247
    {
248 4
        $occurences = preg_match_all(self::REGEX_UPPER_CASE, $this->configuration->checkString());
249
250 4
        if ($occurences >= $this->configuration->rules()->get(Plexity::RULE_UPPER)) {
251 2
            return true;
252
        }
253
254 2
        return false;
255
    }
256
257
    /**
258
     * Validates the lower case requirements.
259
     * @return boolean
260
     */
261 4
    private function validateLowerCase()
262
    {
263 4
        $occurrences = preg_match_all(self::REGEX_LOWER_CASE, $this->configuration->checkString());
264
265 4
        if ($occurrences >= $this->configuration->rules()->get(Plexity::RULE_LOWER)) {
266 2
            return true;
267
        }
268
269 2
        return false;
270
    }
271
272
    /**
273
     * Validates the special character requirements.
274
     * @return boolean
275
     */
276 5
    private function validateSpecialCharacters()
277
    {
278 5
        if ($this->countOccurrences($this->specialCharacters,
279 5
                $this->configuration->checkString()) >= $this->configuration->rules()->get(Plexity::RULE_SPECIAL)) {
280 3
            return true;
281
        }
282 2
        return false;
283
    }
284
285
    /**
286
     * Validates the numeric case requirements.
287
     * @return boolean
288
     */
289 6
    private function validateNumericCharacters()
290
    {
291 6
        if ($this->countOccurrences($this->numbers,
292 6
                $this->configuration->checkString()) >= $this->configuration->rules()->get(Plexity::RULE_NUMERIC)) {
293 3
            return true;
294
        }
295 3
        return false;
296
    }
297
298
    /**
299
     * Validates the minimum string length requirements.
300
     * @return boolean
301
     */
302 6
    private function validateLengthMin()
303
    {
304 6
        if (strlen($this->configuration->checkString()) >= $this->configuration->rules()->get(Plexity::RULE_LENGTH_MIN)) {
305 4
            return true;
306
        }
307 2
        return false;
308
    }
309
310
    /**
311
     * Validates the maximum string length requirements.
312
     * @return boolean
313
     */
314 5
    private function validateLengthMax()
315
    {
316 5
        if (strlen($this->configuration->checkString()) <= $this->configuration->rules()->get(Plexity::RULE_LENGTH_MAX)) {
317 3
            return true;
318
        }
319 2
        return false;
320
    }
321
322
    /**
323
     * Validates the not_in requirements against a simple array.
324
     * @return void
325
     * @throws ValidationException
326
     */
327 2
    private function validateNotInArray()
328
    {
329 2
        if (in_array($this->configuration->checkString(),
330 2
            (array)$this->configuration->rules()->get(Plexity::RULE_NOT_IN))) {
331 1
            throw new ValidationException('The string exists in the list of disallowed values requirements.');
332
        }
333
    }
334
335
    /**
336
     * Validates the not_in requirements against an implementation of PasswordHistoryInterface.
337
     * @return void
338
     * @throws ValidationException
339
     */
340 2
    private function validateNotInPasswordHistoryImplementation()
341
    {
342 2
        if (($this->configuration->rules()->get(Plexity::RULE_NOT_IN))->checkHistory($this->configuration->checkString())) {
343 1
            throw new ValidationException('The string exists in the list of disallowed values requirements.');
344
        }
345
    }
346
347
    /**
348
     * Count the number of occurrences of a character or string in a string.
349
     * @param array<mixed> $needles The character/string to count occurrences of.
350
     * @param string $haystack The string to check against.
351
     * @return int The number of occurrences.
352
     */
353 11
    private function countOccurrences(array $needles, $haystack)
354
    {
355 11
        $count = 0;
356 11
        foreach ($needles as $char) {
357 11
            $count += substr_count($haystack, $char);
358
        }
359 11
        return $count;
360
    }
361
362
    /**
363
     * Validates the not_having requirements against a simple array.
364
     * @return void
365
     * @throws ValidationException
366
     */
367 2
    private function validateNotHaving()
368
    {
369 2
        foreach((array)$this->configuration->rules()->get(Plexity::RULE_NOT_HAVING) as $needle){
370 2
            if ( stripos($this->configuration->checkString() , $needle) !== false) {
371 1
                throw new ValidationException('The string contains word from the list of disallowed values requirements.');
372
            }
373
        }
374
    }
375
}
376