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