Completed
Push — master ( a69d41...0d2748 )
by Bobby
15s queued 13s
created

Validator::validateNotHaving()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 3
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 5
ccs 0
cts 0
cp 0
crap 12
rs 10
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 31
    public function validate(Plexity $configuration)
105
    {
106 31
        $this->configuration = $configuration;
107 31
        $this->checkMinimumLength();
108 29
        $this->checkMaximumLength();
109 27
        $this->checkLowerCase();
110 25
        $this->checkUpperCase();
111 23
        $this->checkNumericCharacters();
112 20
        $this->checkSpecialCharacters();
113 18
        $this->checkNotIn();
114 16
        $this->checkNotHaving();
115
        return true;
116
    }
117
118
    /**
119
     * Checks the minimum length requirement.
120
     * @return void
121
     * @throws ValidationException
122 31
     */
123
    public function checkMinimumLength()
124 31
    {
125 6
        if ($this->configuration->rules()->get(Plexity::RULE_LENGTH_MIN) > 0) {
126 2
            if (!$this->validateLengthMin()) {
127
                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 29
     */
137
    public function checkMaximumLength()
138 29
    {
139 5
        if ($this->configuration->rules()->get(Plexity::RULE_LENGTH_MAX) > 0) {
140 2
            if (!$this->validateLengthMax()) {
141
                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 27
     */
151
    public function checkLowerCase()
152 27
    {
153 4
        if ($this->configuration->rules()->get(Plexity::RULE_LOWER) > 0) {
154 2
            if (!$this->validateLowerCase()) {
155
                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 25
     */
165
    public function checkUpperCase()
166 25
    {
167 4
        if ($this->configuration->rules()->get(Plexity::RULE_UPPER) > 0) {
168 2
            if (!$this->validateUpperCase()) {
169
                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 23
     */
179
    public function checkNumericCharacters()
180 23
    {
181 6
        if ($this->configuration->rules()->get(Plexity::RULE_NUMERIC) > 0) {
182 3
            if (!$this->validateNumericCharacters()) {
183
                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 20
     */
193
    public function checkSpecialCharacters()
194 20
    {
195 5
        if ($this->configuration->rules()->get(Plexity::RULE_SPECIAL) > 0) {
196 2
            if (!$this->validateSpecialCharacters()) {
197
                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 18
     */
207
    public function checkNotIn()
208
    {
209 18
210 14
        if ($this->configuration->rules()->get(Plexity::RULE_NOT_IN) === null) {
211
            return;
212
        }
213 4
214 2
        if ($this->configuration->rules()->get(Plexity::RULE_NOT_IN) instanceof PasswordHistoryInterface) {
215
            $this->validateNotInPasswordHistoryImplementation();
216
        }
217 3
218 2
        if (is_array($this->configuration->rules()->get(Plexity::RULE_NOT_IN)) && count($this->configuration->rules()->get(Plexity::RULE_NOT_IN)) > 0) {
0 ignored issues
show
Bug introduced by
It seems like $this->configuration->ru...y\Plexity::RULE_NOT_IN) 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

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