Do not redirect to HTTPS when it's not supported
[lhc/web/wiklou.git] / tests / phpunit / includes / auth / AuthenticationRequestTest.php
1 <?php
2
3 namespace MediaWiki\Auth;
4
5 /**
6 * @group AuthManager
7 * @covers MediaWiki\Auth\AuthenticationRequest
8 */
9 class AuthenticationRequestTest extends \MediaWikiTestCase {
10 protected function setUp() {
11 global $wgDisableAuthManager;
12
13 parent::setUp();
14 if ( $wgDisableAuthManager ) {
15 $this->markTestSkipped( '$wgDisableAuthManager is set' );
16 }
17 }
18
19 public function testBasics() {
20 $mock = $this->getMockForAbstractClass( AuthenticationRequest::class );
21
22 $this->assertSame( get_class( $mock ), $mock->getUniqueId() );
23
24 $this->assertType( 'array', $mock->getMetadata() );
25
26 $ret = $mock->describeCredentials();
27 $this->assertInternalType( 'array', $ret );
28 $this->assertArrayHasKey( 'provider', $ret );
29 $this->assertInstanceOf( 'Message', $ret['provider'] );
30 $this->assertArrayHasKey( 'account', $ret );
31 $this->assertInstanceOf( 'Message', $ret['account'] );
32 }
33
34 public function testLoadRequestsFromSubmission() {
35 $mb = $this->getMockBuilder( AuthenticationRequest::class )
36 ->setMethods( [ 'loadFromSubmission' ] );
37
38 $data = [ 'foo', 'bar' ];
39
40 $req1 = $mb->getMockForAbstractClass();
41 $req1->expects( $this->once() )->method( 'loadFromSubmission' )
42 ->with( $this->identicalTo( $data ) )
43 ->will( $this->returnValue( false ) );
44
45 $req2 = $mb->getMockForAbstractClass();
46 $req2->expects( $this->once() )->method( 'loadFromSubmission' )
47 ->with( $this->identicalTo( $data ) )
48 ->will( $this->returnValue( true ) );
49
50 $this->assertSame(
51 [ $req2 ],
52 AuthenticationRequest::loadRequestsFromSubmission( [ $req1, $req2 ], $data )
53 );
54 }
55
56 public function testGetRequestByClass() {
57 $mb = $this->getMockBuilder(
58 AuthenticationRequest::class, 'AuthenticationRequestTest_AuthenticationRequest2'
59 );
60
61 $reqs = [
62 $this->getMockForAbstractClass(
63 AuthenticationRequest::class, [], 'AuthenticationRequestTest_AuthenticationRequest1'
64 ),
65 $mb->getMockForAbstractClass(),
66 $mb->getMockForAbstractClass(),
67 $this->getMockForAbstractClass(
68 PasswordAuthenticationRequest::class, [],
69 'AuthenticationRequestTest_PasswordAuthenticationRequest'
70 ),
71 ];
72
73 $this->assertNull( AuthenticationRequest::getRequestByClass(
74 $reqs, 'AuthenticationRequestTest_AuthenticationRequest0'
75 ) );
76 $this->assertSame( $reqs[0], AuthenticationRequest::getRequestByClass(
77 $reqs, 'AuthenticationRequestTest_AuthenticationRequest1'
78 ) );
79 $this->assertNull( AuthenticationRequest::getRequestByClass(
80 $reqs, 'AuthenticationRequestTest_AuthenticationRequest2'
81 ) );
82 $this->assertNull( AuthenticationRequest::getRequestByClass(
83 $reqs, PasswordAuthenticationRequest::class
84 ) );
85 $this->assertNull( AuthenticationRequest::getRequestByClass(
86 $reqs, 'ClassThatDoesNotExist'
87 ) );
88
89 $this->assertNull( AuthenticationRequest::getRequestByClass(
90 $reqs, 'AuthenticationRequestTest_AuthenticationRequest0', true
91 ) );
92 $this->assertSame( $reqs[0], AuthenticationRequest::getRequestByClass(
93 $reqs, 'AuthenticationRequestTest_AuthenticationRequest1', true
94 ) );
95 $this->assertNull( AuthenticationRequest::getRequestByClass(
96 $reqs, 'AuthenticationRequestTest_AuthenticationRequest2', true
97 ) );
98 $this->assertSame( $reqs[3], AuthenticationRequest::getRequestByClass(
99 $reqs, PasswordAuthenticationRequest::class, true
100 ) );
101 $this->assertNull( AuthenticationRequest::getRequestByClass(
102 $reqs, 'ClassThatDoesNotExist', true
103 ) );
104 }
105
106 public function testGetUsernameFromRequests() {
107 $mb = $this->getMockBuilder( AuthenticationRequest::class );
108
109 for ( $i = 0; $i < 3; $i++ ) {
110 $req = $mb->getMockForAbstractClass();
111 $req->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [
112 'username' => [
113 'type' => 'string',
114 ],
115 ] ) );
116 $reqs[] = $req;
117 }
118
119 $req = $mb->getMockForAbstractClass();
120 $req->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [] ) );
121 $req->username = 'baz';
122 $reqs[] = $req;
123
124 $this->assertNull( AuthenticationRequest::getUsernameFromRequests( $reqs ) );
125
126 $reqs[1]->username = 'foo';
127 $this->assertSame( 'foo', AuthenticationRequest::getUsernameFromRequests( $reqs ) );
128
129 $reqs[0]->username = 'foo';
130 $reqs[2]->username = 'foo';
131 $this->assertSame( 'foo', AuthenticationRequest::getUsernameFromRequests( $reqs ) );
132
133 $reqs[1]->username = 'bar';
134 try {
135 AuthenticationRequest::getUsernameFromRequests( $reqs );
136 $this->fail( 'Expected exception not thrown' );
137 } catch ( \UnexpectedValueException $ex ) {
138 $this->assertSame(
139 'Conflicting username fields: "bar" from ' .
140 get_class( $reqs[1] ) . '::$username vs. "foo" from ' .
141 get_class( $reqs[0] ) . '::$username',
142 $ex->getMessage()
143 );
144 }
145 }
146
147 public function testMergeFieldInfo() {
148 $msg = wfMessage( 'foo' );
149
150 $req1 = $this->getMock( AuthenticationRequest::class );
151 $req1->required = AuthenticationRequest::REQUIRED;
152 $req1->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [
153 'string1' => [
154 'type' => 'string',
155 'label' => $msg,
156 'help' => $msg,
157 ],
158 'string2' => [
159 'type' => 'string',
160 'label' => $msg,
161 'help' => $msg,
162 ],
163 'optional' => [
164 'type' => 'string',
165 'label' => $msg,
166 'help' => $msg,
167 'optional' => true,
168 ],
169 'select' => [
170 'type' => 'select',
171 'options' => [ 'foo' => $msg, 'baz' => $msg ],
172 'label' => $msg,
173 'help' => $msg,
174 ],
175 ] ) );
176
177 $req2 = $this->getMock( AuthenticationRequest::class );
178 $req2->required = AuthenticationRequest::REQUIRED;
179 $req2->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [
180 'string1' => [
181 'type' => 'string',
182 'label' => $msg,
183 'help' => $msg,
184 ],
185 'string3' => [
186 'type' => 'string',
187 'label' => $msg,
188 'help' => $msg,
189 ],
190 'select' => [
191 'type' => 'select',
192 'options' => [ 'bar' => $msg, 'baz' => $msg ],
193 'label' => $msg,
194 'help' => $msg,
195 ],
196 ] ) );
197
198 $req3 = $this->getMock( AuthenticationRequest::class );
199 $req3->required = AuthenticationRequest::REQUIRED;
200 $req3->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [
201 'string1' => [
202 'type' => 'checkbox',
203 'label' => $msg,
204 'help' => $msg,
205 ],
206 ] ) );
207
208 $req4 = $this->getMock( AuthenticationRequest::class );
209 $req4->required = AuthenticationRequest::REQUIRED;
210 $req4->expects( $this->any() )->method( 'getFieldInfo' )->will( $this->returnValue( [] ) );
211
212 // Basic combining
213
214 $fields = AuthenticationRequest::mergeFieldInfo( [ $req1 ] );
215 $expect = $req1->getFieldInfo();
216 foreach ( $expect as $name => &$options ) {
217 $options['optional'] = !empty( $options['optional'] );
218 }
219 unset( $options );
220 $this->assertEquals( $expect, $fields );
221
222 $fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req4 ] );
223 $this->assertEquals( $expect, $fields );
224
225 try {
226 AuthenticationRequest::mergeFieldInfo( [ $req1, $req3 ] );
227 $this->fail( 'Expected exception not thrown' );
228 } catch ( \UnexpectedValueException $ex ) {
229 $this->assertSame(
230 'Field type conflict for "string1", "string" vs "checkbox"',
231 $ex->getMessage()
232 );
233 }
234
235 $fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req2 ] );
236 $expect += $req2->getFieldInfo();
237 $expect['string2']['optional'] = false;
238 $expect['string3']['optional'] = false;
239 $expect['select']['options']['bar'] = $msg;
240 $this->assertEquals( $expect, $fields );
241
242 // Combining with something not required
243
244 $req1->required = AuthenticationRequest::PRIMARY_REQUIRED;
245
246 $fields = AuthenticationRequest::mergeFieldInfo( [ $req1 ] );
247 $expect = $req1->getFieldInfo();
248 foreach ( $expect as $name => &$options ) {
249 $options['optional'] = true;
250 }
251 unset( $options );
252 $this->assertEquals( $expect, $fields );
253
254 $fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req2 ] );
255 $expect += $req2->getFieldInfo();
256 $expect['string1']['optional'] = false;
257 $expect['string3']['optional'] = false;
258 $expect['select']['optional'] = false;
259 $expect['select']['options']['bar'] = $msg;
260 $this->assertEquals( $expect, $fields );
261 }
262
263 /**
264 * @dataProvider provideLoadFromSubmission
265 * @param array $fieldInfo
266 * @param array $data
267 * @param array|bool $expectState
268 */
269 public function testLoadFromSubmission( $fieldInfo, $data, $expectState ) {
270 $mock = $this->getMockForAbstractClass( AuthenticationRequest::class );
271 $mock->expects( $this->any() )->method( 'getFieldInfo' )
272 ->will( $this->returnValue( $fieldInfo ) );
273
274 $ret = $mock->loadFromSubmission( $data );
275 if ( is_array( $expectState ) ) {
276 $this->assertTrue( $ret );
277 $expect = call_user_func( [ get_class( $mock ), '__set_state' ], $expectState );
278 $this->assertEquals( $expect, $mock );
279 } else {
280 $this->assertFalse( $ret );
281 }
282 }
283
284 public static function provideLoadFromSubmission() {
285 return [
286 'No fields' => [
287 [],
288 $data = [ 'foo' => 'bar' ],
289 false
290 ],
291
292 'Simple field' => [
293 [
294 'field' => [
295 'type' => 'string',
296 ],
297 ],
298 $data = [ 'field' => 'string!' ],
299 $data
300 ],
301 'Simple field, not supplied' => [
302 [
303 'field' => [
304 'type' => 'string',
305 ],
306 ],
307 [],
308 false
309 ],
310 'Simple field, empty' => [
311 [
312 'field' => [
313 'type' => 'string',
314 ],
315 ],
316 [ 'field' => '' ],
317 false
318 ],
319 'Simple field, optional, not supplied' => [
320 [
321 'field' => [
322 'type' => 'string',
323 'optional' => true,
324 ],
325 ],
326 [],
327 false
328 ],
329 'Simple field, optional, empty' => [
330 [
331 'field' => [
332 'type' => 'string',
333 'optional' => true,
334 ],
335 ],
336 $data = [ 'field' => '' ],
337 $data
338 ],
339
340 'Checkbox, checked' => [
341 [
342 'check' => [
343 'type' => 'checkbox',
344 ],
345 ],
346 [ 'check' => '' ],
347 [ 'check' => true ]
348 ],
349 'Checkbox, unchecked' => [
350 [
351 'check' => [
352 'type' => 'checkbox',
353 ],
354 ],
355 [],
356 false
357 ],
358 'Checkbox, optional, unchecked' => [
359 [
360 'check' => [
361 'type' => 'checkbox',
362 'optional' => true,
363 ],
364 ],
365 [],
366 [ 'check' => false ]
367 ],
368
369 'Button, used' => [
370 [
371 'push' => [
372 'type' => 'button',
373 ],
374 ],
375 [ 'push' => '' ],
376 [ 'push' => true ]
377 ],
378 'Button, unused' => [
379 [
380 'push' => [
381 'type' => 'button',
382 ],
383 ],
384 [],
385 false
386 ],
387 'Button, optional, unused' => [
388 [
389 'push' => [
390 'type' => 'button',
391 'optional' => true,
392 ],
393 ],
394 [],
395 [ 'push' => false ]
396 ],
397 'Button, image-style' => [
398 [
399 'push' => [
400 'type' => 'button',
401 ],
402 ],
403 [ 'push_x' => 0, 'push_y' => 0 ],
404 [ 'push' => true ]
405 ],
406
407 'Select' => [
408 [
409 'choose' => [
410 'type' => 'select',
411 'options' => [
412 'foo' => wfMessage( 'mainpage' ),
413 'bar' => wfMessage( 'mainpage' ),
414 ],
415 ],
416 ],
417 $data = [ 'choose' => 'foo' ],
418 $data
419 ],
420 'Select, invalid choice' => [
421 [
422 'choose' => [
423 'type' => 'select',
424 'options' => [
425 'foo' => wfMessage( 'mainpage' ),
426 'bar' => wfMessage( 'mainpage' ),
427 ],
428 ],
429 ],
430 $data = [ 'choose' => 'baz' ],
431 false
432 ],
433 'Multiselect (2)' => [
434 [
435 'choose' => [
436 'type' => 'multiselect',
437 'options' => [
438 'foo' => wfMessage( 'mainpage' ),
439 'bar' => wfMessage( 'mainpage' ),
440 ],
441 ],
442 ],
443 $data = [ 'choose' => [ 'foo', 'bar' ] ],
444 $data
445 ],
446 'Multiselect (1)' => [
447 [
448 'choose' => [
449 'type' => 'multiselect',
450 'options' => [
451 'foo' => wfMessage( 'mainpage' ),
452 'bar' => wfMessage( 'mainpage' ),
453 ],
454 ],
455 ],
456 $data = [ 'choose' => [ 'bar' ] ],
457 $data
458 ],
459 'Multiselect, string for some reason' => [
460 [
461 'choose' => [
462 'type' => 'multiselect',
463 'options' => [
464 'foo' => wfMessage( 'mainpage' ),
465 'bar' => wfMessage( 'mainpage' ),
466 ],
467 ],
468 ],
469 [ 'choose' => 'foo' ],
470 [ 'choose' => [ 'foo' ] ]
471 ],
472 'Multiselect, invalid choice' => [
473 [
474 'choose' => [
475 'type' => 'multiselect',
476 'options' => [
477 'foo' => wfMessage( 'mainpage' ),
478 'bar' => wfMessage( 'mainpage' ),
479 ],
480 ],
481 ],
482 [ 'choose' => [ 'foo', 'baz' ] ],
483 false
484 ],
485 'Multiselect, empty' => [
486 [
487 'choose' => [
488 'type' => 'multiselect',
489 'options' => [
490 'foo' => wfMessage( 'mainpage' ),
491 'bar' => wfMessage( 'mainpage' ),
492 ],
493 ],
494 ],
495 [ 'choose' => [] ],
496 false
497 ],
498 'Multiselect, optional, nothing submitted' => [
499 [
500 'choose' => [
501 'type' => 'multiselect',
502 'options' => [
503 'foo' => wfMessage( 'mainpage' ),
504 'bar' => wfMessage( 'mainpage' ),
505 ],
506 'optional' => true,
507 ],
508 ],
509 [],
510 [ 'choose' => [] ]
511 ],
512 ];
513 }
514 }