[BUGFIX] Notice free FormEngine testing
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / DatabaseUserPermissionCheck.php
1 <?php
2 namespace TYPO3\CMS\Backend\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 TYPO3\CMS\Backend\Form\Exception\AccessDeniedContentEditException;
18 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedEditInternalsException;
19 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
20 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedHookException;
21 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedPageEditException;
22 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedPageNewException;
23 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedRootNodeException;
24 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedTableModifyException;
25 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
26 use TYPO3\CMS\Backend\Utility\BackendUtility;
27 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
28 use TYPO3\CMS\Core\Type\Bitmask\Permission;
29 use TYPO3\CMS\Core\Utility\GeneralUtility;
30
31 /**
32 * Determine user permission for action and check them
33 */
34 class DatabaseUserPermissionCheck implements FormDataProviderInterface
35 {
36 /**
37 * Set userPermissionOnPage to result array and check access rights.
38 *
39 * A couple of different exceptions are thrown here:
40 * * If something weird happens a top level SPL exception is thrown.
41 * This indicates a non recoverable error.
42 * * If user has no access to whatever should be done, an exception that
43 * extends from Form\Exception\AccessDeniedException is thrown. This
44 * can be caught by upper level controller code and can be translated
45 * to a specific error message that is shown to the user depending on
46 * specific exception that is thrown.
47 *
48 * @param array $result
49 * @return array
50 * @throws AccessDeniedException|\LogicException|\RuntimeException
51 */
52 public function addData(array $result)
53 {
54 $backendUser = $this->getBackendUser();
55
56 // Early return for admins
57 if ($backendUser->isAdmin()) {
58 $result['userPermissionOnPage'] = Permission::ALL;
59 return $result;
60 }
61
62 if (!$backendUser->check('tables_modify', $result['tableName'])) {
63 // If user has no modify rights on table, processing is stopped by throwing an
64 // exception immediately. This case can not be circumvented by hooks.
65 throw new AccessDeniedTableModifyException(
66 'No table modify permission for user ' . $backendUser->user['uid'] . ' on table ' . $result['tableName'],
67 1437683248
68 );
69 }
70
71 $exception = null;
72 $userHasAccess = false;
73 $userPermissionOnPage = Permission::NOTHING;
74 if ($result['command'] === 'new') {
75 // A new record is created. Access rights of parent record are important here
76 // @todo: In case of new inline child, parentPageRow should probably be the
77 // @todo: "inlineFirstPid" page - Maybe effectivePid and parentPageRow should be calculated differently then?
78 if (is_array($result['parentPageRow'])) {
79 // Record is added below an existing page
80 $userPermissionOnPage = $backendUser->calcPerms($result['parentPageRow']);
81 if ($result['tableName'] === 'pages') {
82 // New page is created, user needs PAGE_NEW for this
83 if ((bool)($userPermissionOnPage & Permission::PAGE_NEW)) {
84 $userHasAccess = true;
85 } else {
86 $exception = new AccessDeniedPageNewException(
87 'No page new permission for user ' . $backendUser->user['uid'] . ' on page ' . $result['databaseRow']['uid'],
88 1437745640
89 );
90 }
91 } else {
92 // A regular record is added, not a page. User needs CONTENT_EDIT permission
93 if ((bool)($userPermissionOnPage & Permission::CONTENT_EDIT)) {
94 $userHasAccess = true;
95 } else {
96 $exception = new AccessDeniedContentEditException(
97 'No content new permission for user ' . $backendUser->user['uid'] . ' on page ' . $result['parentPageRow']['uid'],
98 1437745759
99 );
100 }
101 }
102 } elseif (BackendUtility::isRootLevelRestrictionIgnored($result['tableName'])) {
103 // Non admin is creating a record on root node for a table that is actively allowed
104 $userHasAccess = true;
105 $userPermissionOnPage = Permission::ALL;
106 } else {
107 // Non admin has no create permission on root node records
108 $exception = new AccessDeniedRootNodeException(
109 'No record creation permission for user ' . $backendUser->user['uid'] . ' on page root node',
110 1437745221
111 );
112 }
113 } else {
114 // A page or a record on a page is edited
115 if ($result['tableName'] === 'pages') {
116 // A page record is edited, check edit rights of this record directly
117 $userPermissionOnPage = $backendUser->calcPerms($result['defaultLanguagePageRow'] ?? $result['databaseRow']);
118 if ((bool)($userPermissionOnPage & Permission::PAGE_EDIT) && $backendUser->check('pagetypes_select', $result['databaseRow'][$result['processedTca']['ctrl']['type']])) {
119 $userHasAccess = true;
120 } else {
121 $exception = new AccessDeniedPageEditException(
122 'No page edit permission for user ' . $backendUser->user['uid'] . ' on page ' . $result['databaseRow']['uid'],
123 1437679336
124 );
125 }
126 } else {
127 // A non page record is edited.
128 if (isset($result['parentPageRow']) && is_array($result['parentPageRow'])) {
129 // If there is a parent page row, check content edit right of user
130 $userPermissionOnPage = $backendUser->calcPerms($result['parentPageRow']);
131 if ((bool)($userPermissionOnPage & Permission::CONTENT_EDIT)) {
132 $userHasAccess = true;
133 } else {
134 $exception = new AccessDeniedContentEditException(
135 'No content edit permission for user ' . $backendUser->user['uid'] . ' on page ' . $result['parentPageRow']['uid'],
136 1437679657
137 );
138 }
139 } elseif (BackendUtility::isRootLevelRestrictionIgnored($result['tableName'])) {
140 // Non admin is editing a record on root node for a table that is actively allowed
141 $userHasAccess = true;
142 $userPermissionOnPage = Permission::ALL;
143 } else {
144 // Non admin has no edit permission on root node records
145 // @todo: This probably needs further handling, see http://review.typo3.org/40835
146 $exception = new AccessDeniedRootNodeException(
147 'No content edit permission for user ' . $backendUser->user['uid'] . ' on page root node',
148 1437679856
149 );
150 }
151 }
152 if ($userHasAccess) {
153 // If general access is allowed, check "recordEditAccessInternals"
154 $userHasAccess = $backendUser->recordEditAccessInternals($result['tableName'], $result['databaseRow']);
155 if (!$userHasAccess) {
156 $exception = new AccessDeniedEditInternalsException(
157 $backendUser->errorMsg,
158 1437687404
159 );
160 }
161 }
162 }
163
164 if ($userHasAccess && $exception) {
165 // Having user access TRUE here and an exception defined must not happen,
166 // indicates an internal error and throws a logic exception
167 throw new \LogicException(
168 'Access was TRUE but an exception was raised as well for table ' . $result['tableName'] . ' and user ' . $backendUser->user['uid'],
169 1437688402
170 );
171 }
172
173 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'] ?? null)) {
174 // A hook may modify the $userHasAccess decision. Previous state is saved to see if a hook changed
175 // a previous decision from TRUE to FALSE to throw a specific exception in this case
176 $userHasAccessBeforeHook = $userHasAccess;
177 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'] as $methodReference) {
178 $parameters = [
179 'table' => $result['tableName'],
180 'uid' => $result['databaseRow']['uid'],
181 'cmd' => $result['command'],
182 'hasAccess' => $userHasAccess,
183 ];
184 $userHasAccess = (bool)GeneralUtility::callUserFunction($methodReference, $parameters, $this);
185 }
186 if ($userHasAccessBeforeHook && !$userHasAccess) {
187 $exception = new AccessDeniedHookException(
188 'Access to table ' . $result['tableName'] . ' for user ' . $backendUser->user['uid'] . ' was denied by a makeEditForm_accessCheck hook',
189 1437689705
190 );
191 }
192 if (!$userHasAccessBeforeHook && $userHasAccess) {
193 // Unset a previous exception if hook allowed access where previous checks didn't
194 $exception = null;
195 }
196 }
197
198 if (!$userHasAccess && !$exception) {
199 // User has no access, but no according exception was defined. This is an
200 // internal error and throws a logic exception.
201 throw new \LogicException(
202 'Access to table ' . $result['tableName'] . ' denied, but no reason given',
203 1437690507
204 );
205 }
206
207 if ($exception) {
208 throw $exception;
209 }
210
211 $result['userPermissionOnPage'] = $userPermissionOnPage;
212
213 return $result;
214 }
215
216 /**
217 * @return BackendUserAuthentication
218 */
219 protected function getBackendUser()
220 {
221 return $GLOBALS['BE_USER'];
222 }
223 }