|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace PasswordGenerator\Generator; |
|
4
|
|
|
|
|
5
|
|
|
use PasswordGenerator\Exception\ImpossibleMinMaxLimitsException; |
|
6
|
|
|
use PasswordGenerator\Exception\InvalidOptionException; |
|
7
|
|
|
|
|
8
|
|
|
/** |
|
9
|
|
|
* Class RequirementPasswordGenerator |
|
10
|
|
|
* |
|
11
|
|
|
* Works just like ComputerPasswordGenerator with the addition of minimum and maximum counts. |
|
12
|
|
|
* |
|
13
|
|
|
* @package Hackzilla\PasswordGenerator\Generator |
|
14
|
|
|
*/ |
|
15
|
|
|
class RequirementPasswordGenerator extends ComputerPasswordGenerator |
|
16
|
|
|
{ |
|
17
|
|
|
private $minimumCounts = array(); |
|
18
|
|
|
private $maximumCounts = array(); |
|
19
|
|
|
private $validOptions = array(); |
|
20
|
|
|
private $dirtyCheck = true; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
*/ |
|
24
|
|
|
public function __construct() |
|
25
|
|
|
{ |
|
26
|
|
|
parent::__construct(); |
|
27
|
|
|
|
|
28
|
|
|
$this->validOptions = array( |
|
29
|
|
|
self::OPTION_UPPER_CASE, |
|
30
|
|
|
self::OPTION_LOWER_CASE, |
|
31
|
|
|
self::OPTION_NUMBERS, |
|
32
|
|
|
self::OPTION_SYMBOLS, |
|
33
|
|
|
); |
|
34
|
|
|
} |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* Generate one password based on options. |
|
38
|
|
|
* |
|
39
|
|
|
* @return string password |
|
40
|
|
|
* @throws ImpossibleMinMaxLimitsException |
|
41
|
|
|
* @throws \Hackzilla\PasswordGenerator\Exception\CharactersNotFoundException |
|
42
|
|
|
*/ |
|
43
|
|
|
public function generatePassword() |
|
44
|
|
|
{ |
|
45
|
|
|
if ($this->dirtyCheck) { |
|
46
|
|
|
if (!$this->validLimits()) { |
|
47
|
|
|
throw new ImpossibleMinMaxLimitsException(); |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
$this->dirtyCheck = false; |
|
51
|
|
|
} |
|
52
|
|
|
|
|
53
|
|
|
do { |
|
54
|
|
|
$password = parent::generatePassword(); |
|
55
|
|
|
} while (!$this->validatePassword($password)); |
|
56
|
|
|
|
|
57
|
|
|
return $password; |
|
58
|
|
|
} |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* Password minimum count for option. |
|
62
|
|
|
* |
|
63
|
|
|
* @param string $option Use class constants |
|
64
|
|
|
* |
|
65
|
|
|
* @return int|null |
|
66
|
|
|
*/ |
|
67
|
|
|
public function getMinimumCount($option) |
|
68
|
|
|
{ |
|
69
|
|
|
return isset($this->minimumCounts[$option]) ? $this->minimumCounts[$option] : null; |
|
70
|
|
|
} |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* Password maximum count for option. |
|
74
|
|
|
* |
|
75
|
|
|
* @param string $option Use class constants |
|
76
|
|
|
* |
|
77
|
|
|
* @return int|null |
|
78
|
|
|
*/ |
|
79
|
|
|
public function getMaximumCount($option) |
|
80
|
|
|
{ |
|
81
|
|
|
return isset($this->maximumCounts[$option]) ? $this->maximumCounts[$option] : null; |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* Set minimum count of option for desired password(s). |
|
86
|
|
|
* |
|
87
|
|
|
* @param string $option Use class constants |
|
88
|
|
|
* @param int|null $characterCount |
|
89
|
|
|
* |
|
90
|
|
|
* @return $this |
|
91
|
|
|
* |
|
92
|
|
|
* @throws InvalidOptionException |
|
93
|
|
|
*/ |
|
94
|
|
View Code Duplication |
public function setMinimumCount($option, $characterCount) |
|
|
|
|
|
|
95
|
|
|
{ |
|
96
|
|
|
$this->dirtyCheck = true; |
|
97
|
|
|
|
|
98
|
|
|
if (!$this->validOption($option)) { |
|
99
|
|
|
throw new InvalidOptionException('Invalid Option'); |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
if (is_null($characterCount)) { |
|
103
|
|
|
unset($this->minimumCounts[$option]); |
|
104
|
|
|
|
|
105
|
|
|
return $this; |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
if (!is_int($characterCount) || $characterCount < 0) { |
|
109
|
|
|
throw new \InvalidArgumentException('Expected non-negative integer'); |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
$this->minimumCounts[$option] = $characterCount; |
|
113
|
|
|
|
|
114
|
|
|
return $this; |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
/** |
|
118
|
|
|
* Set maximum count of option for desired password(s). |
|
119
|
|
|
* |
|
120
|
|
|
* @param string $option Use class constants |
|
121
|
|
|
* @param int|null $characterCount |
|
122
|
|
|
* |
|
123
|
|
|
* @return $this |
|
124
|
|
|
* |
|
125
|
|
|
* @throws InvalidOptionException |
|
126
|
|
|
*/ |
|
127
|
|
View Code Duplication |
public function setMaximumCount($option, $characterCount) |
|
|
|
|
|
|
128
|
|
|
{ |
|
129
|
|
|
$this->dirtyCheck = true; |
|
130
|
|
|
|
|
131
|
|
|
if (!$this->validOption($option)) { |
|
132
|
|
|
throw new InvalidOptionException('Invalid Option'); |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
if (is_null($characterCount)) { |
|
136
|
|
|
unset($this->maximumCounts[$option]); |
|
137
|
|
|
|
|
138
|
|
|
return $this; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
if (!is_int($characterCount) || $characterCount < 0) { |
|
142
|
|
|
throw new \InvalidArgumentException('Expected non-negative integer'); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
$this->maximumCounts[$option] = $characterCount; |
|
146
|
|
|
|
|
147
|
|
|
return $this; |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
public function validLimits() |
|
151
|
|
|
{ |
|
152
|
|
|
$elements = 0; |
|
153
|
|
|
|
|
154
|
|
|
if ($this->getOptionValue(self::OPTION_UPPER_CASE)) { |
|
155
|
|
|
$elements++; |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
if ($this->getOptionValue(self::OPTION_LOWER_CASE)) { |
|
159
|
|
|
$elements++; |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
if ($this->getOptionValue(self::OPTION_NUMBERS)) { |
|
163
|
|
|
$elements++; |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
if ($this->getOptionValue(self::OPTION_SYMBOLS)) { |
|
167
|
|
|
$elements++; |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
// check if there is wiggle room in minimums |
|
171
|
|
|
$total = 0; |
|
172
|
|
|
|
|
173
|
|
|
foreach ($this->minimumCounts as $minOption => $minCount) { |
|
174
|
|
|
$total += $minCount; |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
if ($total > $this->getLength()) { |
|
178
|
|
|
return false; |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
// check if there is wiggle room in maximums |
|
182
|
|
|
if ($elements <= count($this->maximumCounts)) { |
|
183
|
|
|
$total = 0; |
|
184
|
|
|
|
|
185
|
|
|
foreach ($this->maximumCounts as $maxOption => $maxCount) { |
|
186
|
|
|
$total += $maxCount; |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
if ($total < $this->getLength()) { |
|
190
|
|
|
return false; |
|
191
|
|
|
} |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
return true; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* @param string $option |
|
199
|
|
|
* |
|
200
|
|
|
* @return bool |
|
201
|
|
|
*/ |
|
202
|
|
|
public function validOption($option) |
|
203
|
|
|
{ |
|
204
|
|
|
return in_array($option, $this->validOptions, true); |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* Generate $count number of passwords. |
|
209
|
|
|
* |
|
210
|
|
|
* @param int $count Number of passwords to return |
|
211
|
|
|
* |
|
212
|
|
|
* @return array |
|
213
|
|
|
* |
|
214
|
|
|
* @throws \InvalidArgumentException |
|
215
|
|
|
*/ |
|
216
|
|
View Code Duplication |
public function generatePasswords($count = 1) |
|
|
|
|
|
|
217
|
|
|
{ |
|
218
|
|
|
if (!is_int($count)) { |
|
219
|
|
|
throw new \InvalidArgumentException('Expected integer'); |
|
220
|
|
|
} elseif ($count < 0) { |
|
221
|
|
|
throw new \InvalidArgumentException('Expected positive integer'); |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
$passwords = array(); |
|
225
|
|
|
|
|
226
|
|
|
for ($i = 0; $i < $count; $i++) { |
|
227
|
|
|
$passwords[] = $this->generatePassword(); |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
return $passwords; |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
/** |
|
234
|
|
|
* Check password is valid when comparing to minimum and maximum counts of options. |
|
235
|
|
|
* |
|
236
|
|
|
* @param string $password |
|
237
|
|
|
* |
|
238
|
|
|
* @return bool |
|
239
|
|
|
*/ |
|
240
|
|
|
public function validatePassword($password) |
|
241
|
|
|
{ |
|
242
|
|
|
foreach ($this->validOptions as $option) { |
|
243
|
|
|
$minCount = $this->getMinimumCount($option); |
|
244
|
|
|
$maxCount = $this->getMaximumCount($option); |
|
245
|
|
|
$count = strlen(preg_replace('|[^'.preg_quote($this->getParameter($option)).']|', '', $password)); |
|
246
|
|
|
|
|
247
|
|
|
if (!is_null($minCount) && $count < $minCount) { |
|
248
|
|
|
return false; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
if (!is_null($maxCount) && $count > $maxCount) { |
|
252
|
|
|
return false; |
|
253
|
|
|
} |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
return true; |
|
257
|
|
|
} |
|
258
|
|
|
} |
|
259
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.