Completed
Push — master ( b4b3bc...592c97 )
by Bobby
01:18
created

Validator   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 320
Duplicated Lines 30 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 3
dl 96
loc 320
ccs 90
cts 90
cp 1
rs 9.0399
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A validate() 0 12 1
A checkMinimumLength() 8 8 3
A checkMaximumLength() 8 8 3
A checkLowerCase() 8 8 3
A checkUpperCase() 8 8 3
A checkNumericCharacters() 8 8 3
A checkSpecialCharacters() 8 8 3
A checkNotIn() 0 16 5
A validateUpperCase() 10 10 2
A validateLowerCase() 10 10 2
A validateSpecialCharacters() 7 8 2
A validateNumericCharacters() 7 8 2
A validateLengthMin() 7 7 2
A validateLengthMax() 7 7 2
A validateNotInArray() 0 7 2
A validateNotInPasswordHistoryImplementation() 0 6 2
A countOccurrences() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Validator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Validator, and based on these observations, apply Extract Interface, too.

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/passplexity
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
     * Validates all the configured rules and responds as requested.
100
     * @return boolean
101
     * @throws ValidationException
102
     */
103 28
    public function validate(Plexity $configuration)
104
    {
105 28
        $this->configuration = $configuration;
106 28
        $this->checkMinimumLength();
107 26
        $this->checkMaximumLength();
108 24
        $this->checkLowerCase();
109 22
        $this->checkUpperCase();
110 20
        $this->checkNumericCharacters();
111 18
        $this->checkSpecialCharacters();
112 16
        $this->checkNotIn();
113 14
        return true;
114
    }
115
116
    /**
117
     * Checks the minimum length requirement.
118
     * @return void
119
     * @throws ValidationException
120
     */
121 28 View Code Duplication
    public function checkMinimumLength()
122
    {
123 28
        if ($this->configuration->rules()->get(Plexity::RULE_LENGTH_MIN) > 0) {
124 6
            if (!$this->validateLengthMin()) {
125 2
                throw new ValidationException('The length does not meet the minimum length requirements.');
126
            }
127
        }
128 26
    }
129
130
    /**
131
     * Checks the minimum maximum length requirement.
132
     * @return void
133
     * @throws ValidationException
134
     */
135 26 View Code Duplication
    public function checkMaximumLength()
136
    {
137 26
        if ($this->configuration->rules()->get(Plexity::RULE_LENGTH_MAX) > 0) {
138 5
            if (!$this->validateLengthMax()) {
139 2
                throw new ValidationException('The length exceeds the maximum length requirements.');
140
            }
141
        }
142 24
    }
143
144
    /**
145
     * Checks the lowercase character(s) requirement.
146
     * @return void
147
     * @throws ValidationException
148
     */
149 24 View Code Duplication
    public function checkLowerCase()
150
    {
151 24
        if ($this->configuration->rules()->get(Plexity::RULE_LOWER) > 0) {
152 4
            if (!$this->validateLowerCase()) {
153 2
                throw new ValidationException('The string failed to meet the lower case requirements.');
154
            }
155
        }
156 22
    }
157
158
    /**
159
     * Checks the upper case character(s) requirement.
160
     * @return void
161
     * @throws ValidationException
162
     */
163 22 View Code Duplication
    public function checkUpperCase()
164
    {
165 22
        if ($this->configuration->rules()->get(Plexity::RULE_UPPER) > 0) {
166 4
            if (!$this->validateUpperCase()) {
167 2
                throw new ValidationException('The string failed to meet the upper case requirements.');
168
            }
169
        }
170 20
    }
171
172
    /**
173
     * Checks the numeric character(s) requirement.
174
     * @return void
175
     * @throws ValidationException
176
     */
177 20 View Code Duplication
    public function checkNumericCharacters()
178
    {
179 20
        if ($this->configuration->rules()->get(Plexity::RULE_NUMERIC) > 0) {
180 4
            if (!$this->validateNumericCharacters()) {
181 2
                throw new ValidationException('The string failed to meet the numeric character requirements.');
182
            }
183
        }
184 18
    }
185
186
    /**
187
     * Checks the special character(s) requirement.
188
     * @return void
189
     * @throws ValidationException
190
     */
191 18 View Code Duplication
    public function checkSpecialCharacters()
192
    {
193 18
        if ($this->configuration->rules()->get(Plexity::RULE_SPECIAL) > 0) {
194 4
            if (!$this->validateSpecialCharacters()) {
195 2
                throw new ValidationException('The string failed to meet the special character requirements.');
196
            }
197
        }
198 16
    }
199
200
    /**
201
     * Validates if a string is not in a array (password history database).
202
     * @return void
203
     * @throws ValidationException
204
     */
205 16
    public function checkNotIn()
206
    {
207
208 16
        if ($this->configuration->rules()->get(Plexity::RULE_NOT_IN) === null) {
209 12
            return;
210
        }
211
212 4
        if ($this->configuration->rules()->get(Plexity::RULE_NOT_IN) instanceof PasswordHistoryInterface) {
213 2
            $this->validateNotInPasswordHistoryImplementation();
214
        }
215
216 3
        if (is_array($this->configuration->rules()->get(Plexity::RULE_NOT_IN)) && count($this->configuration->rules()->get(Plexity::RULE_NOT_IN)) > 0) {
217 2
            $this->validateNotInArray();
218
        }
219
220 2
    }
221
222
    /**
223
     * Validates the upper case requirements.
224
     * @return boolean
225
     */
226 4 View Code Duplication
    private function validateUpperCase()
227
    {
228 4
        $occurences = preg_match_all(self::REGEX_UPPER_CASE, $this->configuration->checkString());
229
230 4
        if ($occurences >= $this->configuration->rules()->get(Plexity::RULE_UPPER)) {
231 2
            return true;
232
        }
233
234 2
        return false;
235
    }
236
237
    /**
238
     * Validates the lower case requirements.
239
     * @return boolean
240
     */
241 4 View Code Duplication
    private function validateLowerCase()
242
    {
243 4
        $occurrences = preg_match_all(self::REGEX_LOWER_CASE, $this->configuration->checkString());
244
245 4
        if ($occurrences >= $this->configuration->rules()->get(Plexity::RULE_LOWER)) {
246 2
            return true;
247
        }
248
249 2
        return false;
250
    }
251
252
    /**
253
     * Validates the special character requirements.
254
     * @return boolean
255
     */
256 4 View Code Duplication
    private function validateSpecialCharacters()
257
    {
258 4
        if ($this->countOccurrences($this->specialCharacters,
259 4
                $this->configuration->checkString()) >= $this->configuration->rules()->get(Plexity::RULE_SPECIAL)) {
260 2
            return true;
261
        }
262 2
        return false;
263
    }
264
265
    /**
266
     * Validates the numeric case requirements.
267
     * @return boolean
268
     */
269 4 View Code Duplication
    private function validateNumericCharacters()
270
    {
271 4
        if ($this->countOccurrences($this->numbers,
272 4
                $this->configuration->checkString()) >= $this->configuration->rules()->get(Plexity::RULE_NUMERIC)) {
273 2
            return true;
274
        }
275 2
        return false;
276
    }
277
278
    /**
279
     * Validates the minimum string length requirements.
280
     * @return boolean
281
     */
282 6 View Code Duplication
    private function validateLengthMin()
283
    {
284 6
        if (strlen($this->configuration->checkString()) >= $this->configuration->rules()->get(Plexity::RULE_LENGTH_MIN)) {
285 4
            return true;
286
        }
287 2
        return false;
288
    }
289
290
    /**
291
     * Validates the maximum string length requirements.
292
     * @return boolean
293
     */
294 5 View Code Duplication
    private function validateLengthMax()
295
    {
296 5
        if (strlen($this->configuration->checkString()) <= $this->configuration->rules()->get(Plexity::RULE_LENGTH_MAX)) {
297 3
            return true;
298
        }
299 2
        return false;
300
    }
301
302
    /**
303
     * Validates the not_in requirements against a simple array.
304
     * @return void
305
     * @throws ValidationException
306
     */
307 2
    private function validateNotInArray()
308
    {
309 2
        if (in_array($this->configuration->checkString(),
310 2
            (array)$this->configuration->rules()->get(Plexity::RULE_NOT_IN))) {
311 1
            throw new ValidationException('The string exists in the list of disallowed values requirements.');
312
        }
313 1
    }
314
315
    /**
316
     * Validates the not_in requirements against an implementation of PasswordHistoryInterface.
317
     * @return void
318
     * @throws ValidationException
319
     */
320 2
    private function validateNotInPasswordHistoryImplementation()
321
    {
322 2
        if (($this->configuration->rules()->get(Plexity::RULE_NOT_IN))->checkHistory($this->configuration->checkString())) {
323 1
            throw new ValidationException('The string exists in the list of disallowed values requirements.');
324
        }
325 1
    }
326
327
    /**
328
     * Count the number of occurrences of a character or string in a string.
329
     * @param array<mixed> $needles The character/string to count occurrences of.
330
     * @param string $haystack The string to check against.
331
     * @return int The number of occurrences.
332
     */
333 8
    private function countOccurrences(array $needles, $haystack)
334
    {
335 8
        $count = 0;
336 8
        foreach ($needles as $char) {
337 8
            $count += substr_count($haystack, $char);
338
        }
339 8
        return $count;
340
    }
341
}
342