[BUGFIX] Do not allow users to edit doktypes without permissions
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Tests / Unit / Form / FormDataProvider / DatabaseUserPermissionCheckTest.php
1 <?php
2 namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Prophecy\Argument;
18 use Prophecy\Prophecy\ObjectProphecy;
19 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedContentEditException;
20 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedEditInternalsException;
21 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedHookException;
22 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedPageEditException;
23 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedPageNewException;
24 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedRootNodeException;
25 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedTableModifyException;
26 use TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUserPermissionCheck;
27 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
28 use TYPO3\CMS\Core\Type\Bitmask\Permission;
29
30 /**
31 * Test case
32 */
33 class DatabaseUserPermissionCheckTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
34 {
35 /**
36 * @var DatabaseUserPermissionCheck
37 */
38 protected $subject;
39
40 /**
41 * @var BackendUserAuthentication | ObjectProphecy
42 */
43 protected $beUserProphecy;
44
45 protected function setUp()
46 {
47 $this->subject = new DatabaseUserPermissionCheck();
48
49 $this->beUserProphecy = $this->prophesize(BackendUserAuthentication::class);
50 $GLOBALS['BE_USER'] = $this->beUserProphecy->reveal();
51 $GLOBALS['BE_USER']->user['uid'] = 42;
52 }
53
54 /**
55 * @test
56 */
57 public function addDataSetsUserPermissionsOnPageForAdminUser()
58 {
59 $this->beUserProphecy->isAdmin()->willReturn(true);
60
61 $result = $this->subject->addData([]);
62
63 $this->assertSame(Permission::ALL, $result['userPermissionOnPage']);
64 }
65
66 /**
67 * @test
68 */
69 public function addDataThrowsExceptionIfUserHasNoTablesModifyPermissionForGivenTable()
70 {
71 $input = [
72 'tableName' => 'tt_content',
73 ];
74 $this->beUserProphecy->isAdmin()->willReturn(false);
75 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(false);
76
77 $this->expectException(AccessDeniedTableModifyException::class);
78 $this->expectExceptionCode(1437683248);
79
80 $this->subject->addData($input);
81 }
82
83 /**
84 * @test
85 */
86 public function addDataThrowsExceptionIfUserHasNoContentEditPermissionsOnPage()
87 {
88 $input = [
89 'tableName' => 'tt_content',
90 'command' => 'edit',
91 'vanillaUid' => 123,
92 'parentPageRow' => [
93 'pid' => 321,
94 ],
95 ];
96 $this->beUserProphecy->isAdmin()->willReturn(false);
97 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
98 $this->beUserProphecy->calcPerms(['pid' => 321])->willReturn(Permission::NOTHING);
99
100 $this->expectException(AccessDeniedContentEditException::class);
101 $this->expectExceptionCode(1437679657);
102
103 $this->subject->addData($input);
104 }
105
106 /**
107 * @test
108 */
109 public function addDataAddsUserPermissionsOnPageForContentIfUserHasCorrespondingPermissions()
110 {
111 $input = [
112 'tableName' => 'tt_content',
113 'command' => 'edit',
114 'vanillaUid' => 123,
115 'parentPageRow' => [
116 'pid' => 321,
117 ],
118 ];
119 $this->beUserProphecy->isAdmin()->willReturn(false);
120 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
121 $this->beUserProphecy->calcPerms(['pid' => 321])->willReturn(Permission::CONTENT_EDIT);
122 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::any())->willReturn(true);
123
124 $result = $this->subject->addData($input);
125
126 $this->assertSame(Permission::CONTENT_EDIT, $result['userPermissionOnPage']);
127 }
128
129 /**
130 * @test
131 */
132 public function addDataThrowsExceptionIfCommandIsEditTableIsPagesAndUserHasNoPagePermissions()
133 {
134 $input = [
135 'tableName' => 'pages',
136 'command' => 'edit',
137 'vanillaUid' => 123,
138 'databaseRow' => [
139 'uid' => 123,
140 'pid' => 321
141 ],
142 ];
143 $this->beUserProphecy->isAdmin()->willReturn(false);
144 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
145 $this->beUserProphecy->calcPerms($input['databaseRow'])->willReturn(Permission::NOTHING);
146
147 $this->expectException(AccessDeniedPageEditException::class);
148 $this->expectExceptionCode(1437679336);
149
150 $this->subject->addData($input);
151 }
152
153 /**
154 * @test
155 */
156 public function addDataThrowsExceptionIfCommandIsEditTableIsPagesAndUserHasNoDoktypePermissions()
157 {
158 $input = [
159 'tableName' => 'pages',
160 'command' => 'edit',
161 'vanillaUid' => 123,
162 'databaseRow' => [
163 'uid' => 123,
164 'pid' => 321,
165 'doktype' => 1,
166 ],
167 ];
168 $this->beUserProphecy->isAdmin()->willReturn(false);
169 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
170 $this->beUserProphecy->check('pagetypes_select', $input['databaseRow']['doktype'])->willReturn(false);
171 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
172 $this->beUserProphecy->calcPerms($input['databaseRow'])->willReturn(Permission::ALL);
173
174 $this->expectException(AccessDeniedPageEditException::class);
175 $this->expectExceptionCode(1437679336);
176
177 $this->subject->addData($input);
178 }
179
180 /**
181 * @test
182 */
183 public function addDataAddsUserPermissionsOnPageIfTableIsPagesAndUserHasPagePermissions()
184 {
185 $input = [
186 'tableName' => 'pages',
187 'command' => 'edit',
188 'vanillaUid' => 123,
189 'databaseRow' => [
190 'uid' => 123,
191 'pid' => 321,
192 'doktype' => 1,
193 ],
194 ];
195 $this->beUserProphecy->isAdmin()->willReturn(false);
196 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
197 $this->beUserProphecy->check('pagetypes_select', $input['databaseRow']['doktype'])->willReturn(true);
198 $this->beUserProphecy->calcPerms($input['databaseRow'])->willReturn(Permission::PAGE_EDIT);
199 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
200
201 $result = $this->subject->addData($input);
202
203 $this->assertSame(Permission::PAGE_EDIT, $result['userPermissionOnPage']);
204 }
205
206 /**
207 * @test
208 */
209 public function addDataSetsPermissionsToAllIfRootLevelRestrictionForTableIsIgnoredForContentEditRecord()
210 {
211 $input = [
212 'tableName' => 'tt_content',
213 'command' => 'edit',
214 'vanillaUid' => 123,
215 'databaseRow' => [
216 'uid' => 123,
217 'pid' => 0,
218 ],
219 ];
220 $this->beUserProphecy->isAdmin()->willReturn(false);
221 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
222 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
223 $GLOBALS['TCA'][$input['tableName']]['ctrl']['security']['ignoreRootLevelRestriction'] = true;
224
225 $result = $this->subject->addData($input);
226
227 $this->assertSame(Permission::ALL, $result['userPermissionOnPage']);
228 }
229
230 /**
231 * @test
232 */
233 public function addDataThrowsExceptionIfRootNodeShouldBeEditedWithoutPermissions()
234 {
235 $input = [
236 'tableName' => 'tt_content',
237 'command' => 'edit',
238 'vanillaUid' => 123,
239 'databaseRow' => [
240 'uid' => 123,
241 'pid' => 0,
242 ],
243 ];
244 $this->beUserProphecy->isAdmin()->willReturn(false);
245 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
246 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
247
248 $this->expectException(AccessDeniedRootNodeException::class);
249 $this->expectExceptionCode(1437679856);
250
251 $this->subject->addData($input);
252 }
253
254 /**
255 * @test
256 */
257 public function addDataThrowsExceptionIfRecordEditAccessInternalsReturnsFalse()
258 {
259 $input = [
260 'tableName' => 'tt_content',
261 'command' => 'edit',
262 'vanillaUid' => 123,
263 'parentPageRow' => [
264 'uid' => 123,
265 'pid' => 321,
266 ],
267 ];
268 $this->beUserProphecy->isAdmin()->willReturn(false);
269 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
270 $this->beUserProphecy->calcPerms($input['parentPageRow'])->willReturn(Permission::ALL);
271 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(false);
272
273 $this->expectException(AccessDeniedEditInternalsException::class);
274 $this->expectExceptionCode(1437687404);
275
276 $this->subject->addData($input);
277 }
278
279 /**
280 * @test
281 */
282 public function addDataThrowsExceptionForNewContentRecordWithoutPermissions()
283 {
284 $input = [
285 'tableName' => 'tt_content',
286 'command' => 'new',
287 'vanillaUid' => 123,
288 'parentPageRow' => [
289 'uid' => 123,
290 'pid' => 321,
291 ],
292 ];
293 $this->beUserProphecy->isAdmin()->willReturn(false);
294 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
295 $this->beUserProphecy->calcPerms($input['parentPageRow'])->willReturn(Permission::NOTHING);
296
297 $this->expectException(AccessDeniedContentEditException::class);
298 $this->expectExceptionCode(1437745759);
299
300 $this->subject->addData($input);
301 }
302
303 /**
304 * @test
305 */
306 public function addDataThrowsExceptionForNewPageWithoutPermissions()
307 {
308 $input = [
309 'tableName' => 'pages',
310 'command' => 'new',
311 'vanillaUid' => 123,
312 'parentPageRow' => [
313 'uid' => 123,
314 'pid' => 321,
315 ],
316 ];
317 $this->beUserProphecy->isAdmin()->willReturn(false);
318 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
319 $this->beUserProphecy->calcPerms($input['parentPageRow'])->willReturn(Permission::NOTHING);
320
321 $this->expectException(AccessDeniedPageNewException::class);
322 $this->expectExceptionCode(1437745640);
323
324 $this->subject->addData($input);
325 }
326
327 /**
328 * @test
329 */
330 public function addDataThrowsExceptionIfHookDeniesAccess()
331 {
332 $input = [
333 'tableName' => 'tt_content',
334 'command' => 'edit',
335 'vanillaUid' => 123,
336 'parentPageRow' => [
337 'uid' => 123,
338 'pid' => 321,
339 ],
340 ];
341 $this->beUserProphecy->isAdmin()->willReturn(false);
342 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
343 $this->beUserProphecy->calcPerms($input['parentPageRow'])->willReturn(Permission::ALL);
344 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
345
346 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'] = [
347 'unitTest' => function () {
348 return false;
349 }
350 ];
351
352 $this->expectException(AccessDeniedHookException::class);
353 $this->expectExceptionCode(1437689705);
354
355 $this->subject->addData($input);
356 }
357
358 /**
359 * @test
360 */
361 public function addDataSetsUserPermissionsOnPageForNewPageIfPageNewIsDeniedAndHookAllowsAccess()
362 {
363 $input = [
364 'tableName' => 'pages',
365 'command' => 'new',
366 'vanillaUid' => 123,
367 'parentPageRow' => [
368 'uid' => 123,
369 'pid' => 321,
370 ],
371 ];
372 $this->beUserProphecy->isAdmin()->willReturn(false);
373 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
374 $this->beUserProphecy->calcPerms($input['parentPageRow'])->willReturn(Permission::CONTENT_EDIT);
375 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
376
377 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'] = [
378 'unitTest' => function () {
379 return true;
380 }
381 ];
382
383 $result = $this->subject->addData($input);
384
385 $this->assertSame(Permission::CONTENT_EDIT, $result['userPermissionOnPage']);
386 }
387
388 /**
389 * @test
390 */
391 public function addDataSetsUserPermissionsOnPageForNewPage()
392 {
393 $input = [
394 'tableName' => 'pages',
395 'command' => 'new',
396 'vanillaUid' => 123,
397 'parentPageRow' => [
398 'uid' => 123,
399 'pid' => 321,
400 ],
401 ];
402 $this->beUserProphecy->isAdmin()->willReturn(false);
403 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
404 $this->beUserProphecy->calcPerms($input['parentPageRow'])->willReturn(Permission::PAGE_NEW);
405 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
406
407 $result = $this->subject->addData($input);
408
409 $this->assertSame(Permission::PAGE_NEW, $result['userPermissionOnPage']);
410 }
411
412 /**
413 * @test
414 */
415 public function addDataSetsUserPermissionsOnPageForNewContentRecord()
416 {
417 $input = [
418 'tableName' => 'tt_content',
419 'command' => 'new',
420 'vanillaUid' => 123,
421 'parentPageRow' => [
422 'uid' => 123,
423 'pid' => 321,
424 ],
425 ];
426 $this->beUserProphecy->isAdmin()->willReturn(false);
427 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
428 $this->beUserProphecy->calcPerms($input['parentPageRow'])->willReturn(Permission::CONTENT_EDIT);
429 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
430
431 $result = $this->subject->addData($input);
432
433 $this->assertSame(Permission::CONTENT_EDIT, $result['userPermissionOnPage']);
434 }
435
436 /**
437 * @test
438 */
439 public function addDataSetsPermissionsToAllIfRootLevelRestrictionForTableIsIgnoredForNewContentRecord()
440 {
441 $input = [
442 'tableName' => 'pages',
443 'command' => 'new',
444 'vanillaUid' => 123,
445 'parentPageRow' => null,
446 ];
447 $this->beUserProphecy->isAdmin()->willReturn(false);
448 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
449 $this->beUserProphecy->recordEditAccessInternals($input['tableName'], Argument::cetera())->willReturn(true);
450 $GLOBALS['TCA'][$input['tableName']]['ctrl']['security']['ignoreRootLevelRestriction'] = true;
451
452 $result = $this->subject->addData($input);
453
454 $this->assertSame(Permission::ALL, $result['userPermissionOnPage']);
455 }
456
457 /**
458 * @test
459 */
460 public function addDataThrowsExceptionForNewRecordsOnRootLevelWithoutPermissions()
461 {
462 $input = [
463 'tableName' => 'pages',
464 'command' => 'new',
465 'vanillaUid' => 123,
466 'parentPageRow' => null,
467 ];
468
469 $this->beUserProphecy->isAdmin()->willReturn(false);
470 $this->beUserProphecy->check('tables_modify', $input['tableName'])->willReturn(true);
471
472 $this->expectException(AccessDeniedRootNodeException::class);
473 $this->expectExceptionCode(1437745221);
474
475 $this->subject->addData($input);
476 }
477 }