Merge "selenium: Re-add all tests and wdio-mocha-framework, now v0.6.4"
[lhc/web/wiklou.git] / tests / phpunit / includes / password / UserPasswordPolicyTest.php
1 <?php
2 /**
3 * Testing for password-policy enforcement, based on a user's groups.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23 /**
24 * @group Database
25 * @covers UserPasswordPolicy
26 */
27 class UserPasswordPolicyTest extends MediaWikiTestCase {
28
29 protected $tablesUsed = [ 'user', 'user_groups' ];
30
31 protected $policies = [
32 'checkuser' => [
33 'MinimalPasswordLength' => [ 'value' => 10, 'forceChange' => true ],
34 'MinimumPasswordLengthToLogin' => 6,
35 'PasswordCannotMatchUsername' => true,
36 ],
37 'sysop' => [
38 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true ],
39 'MinimumPasswordLengthToLogin' => 1,
40 'PasswordCannotMatchUsername' => true,
41 ],
42 'bureaucrat' => [
43 'MinimalPasswordLength' => [
44 'value' => 6,
45 'suggestChangeOnLogin' => false,
46 'forceChange' => true,
47 ],
48 'PasswordCannotMatchUsername' => true,
49 ],
50 'default' => [
51 'MinimalPasswordLength' => 4,
52 'MinimumPasswordLengthToLogin' => 1,
53 'PasswordCannotMatchBlacklist' => true,
54 'MaximalPasswordLength' => 4096,
55 // test null handling
56 'PasswordCannotMatchUsername' => null,
57 ],
58 ];
59
60 protected $checks = [
61 'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength',
62 'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin',
63 'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername',
64 'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist',
65 'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
66 ];
67
68 private function getUserPasswordPolicy() {
69 return new UserPasswordPolicy( $this->policies, $this->checks );
70 }
71
72 public function testGetPoliciesForUser() {
73 $upp = $this->getUserPasswordPolicy();
74
75 $user = $this->getTestUser( [ 'sysop' ] )->getUser();
76 $this->assertArrayEquals(
77 [
78 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true ],
79 'MinimumPasswordLengthToLogin' => 1,
80 'PasswordCannotMatchUsername' => true,
81 'PasswordCannotMatchBlacklist' => true,
82 'MaximalPasswordLength' => 4096,
83 ],
84 $upp->getPoliciesForUser( $user )
85 );
86
87 $user = $this->getTestUser( [ 'sysop', 'checkuser' ] )->getUser();
88 $this->assertArrayEquals(
89 [
90 'MinimalPasswordLength' => [
91 'value' => 10,
92 'forceChange' => true,
93 'suggestChangeOnLogin' => true
94 ],
95 'MinimumPasswordLengthToLogin' => 6,
96 'PasswordCannotMatchUsername' => true,
97 'PasswordCannotMatchBlacklist' => true,
98 'MaximalPasswordLength' => 4096,
99 ],
100 $upp->getPoliciesForUser( $user )
101 );
102 }
103
104 public function testGetPoliciesForGroups() {
105 $effective = UserPasswordPolicy::getPoliciesForGroups(
106 $this->policies,
107 [ 'user', 'checkuser', 'sysop' ],
108 $this->policies['default']
109 );
110
111 $this->assertArrayEquals(
112 [
113 'MinimalPasswordLength' => [
114 'value' => 10,
115 'forceChange' => true,
116 'suggestChangeOnLogin' => true
117 ],
118 'MinimumPasswordLengthToLogin' => 6,
119 'PasswordCannotMatchUsername' => true,
120 'PasswordCannotMatchBlacklist' => true,
121 'MaximalPasswordLength' => 4096,
122 ],
123 $effective
124 );
125 }
126
127 /**
128 * @dataProvider provideCheckUserPassword
129 */
130 public function testCheckUserPassword( $groups, $password, StatusValue $expectedStatus ) {
131 $upp = $this->getUserPasswordPolicy();
132 $user = $this->getTestUser( $groups )->getUser();
133
134 $status = $upp->checkUserPassword( $user, $password );
135 $this->assertSame( $expectedStatus->isGood(), $status->isGood(), 'password valid' );
136 $this->assertSame( $expectedStatus->isOK(), $status->isOK(), 'can login' );
137 $this->assertSame( $expectedStatus->getValue(), $status->getValue(), 'flags' );
138 }
139
140 public function provideCheckUserPassword() {
141 $success = Status::newGood( [] );
142 $warning = Status::newGood( [] );
143 $forceChange = Status::newGood( [ 'forceChange' => true ] );
144 $suggestChangeOnLogin = Status::newGood( [ 'suggestChangeOnLogin' => true ] );
145 $fatal = Status::newGood( [] );
146
147 // the message does not matter, we only test for state and value
148 $warning->warning( 'invalid-password' );
149 $forceChange->warning( 'invalid-password' );
150 $suggestChangeOnLogin->warning( 'invalid-password' );
151 $warning->warning( 'invalid-password' );
152 $fatal->fatal( 'invalid-password' );
153
154 return [
155 'No groups, default policy, password too short to login' => [
156 [],
157 '',
158 $fatal,
159 ],
160 'Default policy, short password' => [
161 [ 'user' ],
162 'aaa',
163 $warning,
164 ],
165 'Sysop with good password' => [
166 [ 'sysop' ],
167 'abcdabcdabcd',
168 $success,
169 ],
170 'Sysop with short password and suggestChangeOnLogin set to true' => [
171 [ 'sysop' ],
172 'abcd',
173 $suggestChangeOnLogin,
174 ],
175 'Checkuser with short password' => [
176 [ 'checkuser' ],
177 'abcdabcd',
178 $forceChange,
179 ],
180 'Bureaucrat bad password with forceChange true, suggestChangeOnLogin false' => [
181 [ 'bureaucrat' ],
182 'short',
183 $forceChange,
184 ],
185 'Checkuser with too short password to login' => [
186 [ 'sysop', 'checkuser' ],
187 'abcd',
188 $fatal,
189 ],
190 ];
191 }
192
193 public function testCheckUserPassword_blacklist() {
194 $upp = $this->getUserPasswordPolicy();
195 $user = User::newFromName( 'Useruser' );
196 $user->addToDatabase();
197
198 $status = $upp->checkUserPassword( $user, 'Passpass' );
199 $this->assertFalse( $status->isGood(), 'password invalid' );
200 $this->assertTrue( $status->isOK(), 'can login' );
201 }
202
203 /**
204 * @dataProvider provideMaxOfPolicies
205 */
206 public function testMaxOfPolicies( $p1, $p2, $max ) {
207 $this->assertArrayEquals(
208 $max,
209 UserPasswordPolicy::maxOfPolicies( $p1, $p2 )
210 );
211 }
212
213 public function provideMaxOfPolicies() {
214 return [
215 'Basic max in p1' => [
216 [ 'MinimalPasswordLength' => 8 ], // p1
217 [ 'MinimalPasswordLength' => 2 ], // p2
218 [ 'MinimalPasswordLength' => 8 ], // max
219 ],
220 'Basic max in p2' => [
221 [ 'MinimalPasswordLength' => 2 ], // p1
222 [ 'MinimalPasswordLength' => 8 ], // p2
223 [ 'MinimalPasswordLength' => 8 ], // max
224 ],
225 'Missing items in p1' => [
226 [
227 'MinimalPasswordLength' => 8,
228 ], // p1
229 [
230 'MinimalPasswordLength' => 2,
231 'PasswordCannotMatchUsername' => 1,
232 ], // p2
233 [
234 'MinimalPasswordLength' => 8,
235 'PasswordCannotMatchUsername' => 1,
236 ], // max
237 ],
238 'Missing items in p2' => [
239 [
240 'MinimalPasswordLength' => 8,
241 'PasswordCannotMatchUsername' => 1,
242 ], // p1
243 [
244 'MinimalPasswordLength' => 2,
245 ], // p2
246 [
247 'MinimalPasswordLength' => 8,
248 'PasswordCannotMatchUsername' => 1,
249 ], // max
250 ],
251 'complex value in p1' => [
252 [
253 'MinimalPasswordLength' => [
254 'value' => 8,
255 'foo' => 1,
256 ],
257 ], // p1
258 [
259 'MinimalPasswordLength' => 2,
260 ], // p2
261 [
262 'MinimalPasswordLength' => [
263 'value' => 8,
264 'foo' => 1,
265 ],
266 ], // max
267 ],
268 'complex value in p2' => [
269 [
270 'MinimalPasswordLength' => 8,
271 ], // p1
272 [
273 'MinimalPasswordLength' => [
274 'value' => 2,
275 'foo' => 1,
276 ],
277 ], // p2
278 [
279 'MinimalPasswordLength' => [
280 'value' => 8,
281 'foo' => 1,
282 ],
283 ], // max
284 ],
285 'complex value in both p1 and p2' => [
286 [
287 'MinimalPasswordLength' => [
288 'value' => 8,
289 'foo' => 1,
290 'baz' => false,
291 ],
292 ], // p1
293 [
294 'MinimalPasswordLength' => [
295 'value' => 2,
296 'bar' => 2,
297 'baz' => true,
298 ],
299 ], // p2
300 [
301 'MinimalPasswordLength' => [
302 'value' => 8,
303 'foo' => 1,
304 'bar' => 2,
305 'baz' => true,
306 ],
307 ], // max
308 ],
309 'complex value in both p1 and p2 #2' => [
310 [
311 'MinimalPasswordLength' => [
312 'value' => 8,
313 'foo' => 1,
314 'baz' => false,
315 ],
316 ], // p1
317 [
318 'MinimalPasswordLength' => [
319 'value' => 2,
320 'bar' => true
321 ],
322 ], // p2
323 [
324 'MinimalPasswordLength' => [
325 'value' => 8,
326 'foo' => 1,
327 'bar' => true,
328 'baz' => false,
329 ],
330 ], // max
331 ],
332 ];
333 }
334
335 }