|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* This file is part of the TYPO3 CMS project. |
|
5
|
|
|
* |
|
6
|
|
|
* It is free software; you can redistribute it and/or modify it under |
|
7
|
|
|
* the terms of the GNU General Public License, either version 2 |
|
8
|
|
|
* of the License, or any later version. |
|
9
|
|
|
* |
|
10
|
|
|
* For the full copyright and license information, please read the |
|
11
|
|
|
* LICENSE.txt file that was distributed with this source code. |
|
12
|
|
|
* |
|
13
|
|
|
* The TYPO3 project - inspiring people to share! |
|
14
|
|
|
*/ |
|
15
|
|
|
|
|
16
|
|
|
namespace TYPO3\CMS\Core\Authentication; |
|
17
|
|
|
|
|
18
|
|
|
use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; |
|
19
|
|
|
use TYPO3\CMS\Backend\Utility\BackendUtility; |
|
20
|
|
|
use TYPO3\CMS\Core\Cache\CacheManager; |
|
21
|
|
|
use TYPO3\CMS\Core\Core\Environment; |
|
22
|
|
|
use TYPO3\CMS\Core\Database\Connection; |
|
23
|
|
|
use TYPO3\CMS\Core\Database\ConnectionPool; |
|
24
|
|
|
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder; |
|
25
|
|
|
use TYPO3\CMS\Core\Database\Query\QueryHelper; |
|
26
|
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; |
|
27
|
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction; |
|
28
|
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction; |
|
29
|
|
|
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction; |
|
30
|
|
|
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; |
|
31
|
|
|
use TYPO3\CMS\Core\Http\ImmediateResponseException; |
|
32
|
|
|
use TYPO3\CMS\Core\Http\RedirectResponse; |
|
33
|
|
|
use TYPO3\CMS\Core\Resource\Exception; |
|
34
|
|
|
use TYPO3\CMS\Core\Resource\Filter\FileNameFilter; |
|
35
|
|
|
use TYPO3\CMS\Core\Resource\Folder; |
|
36
|
|
|
use TYPO3\CMS\Core\Resource\ResourceFactory; |
|
37
|
|
|
use TYPO3\CMS\Core\Resource\ResourceStorage; |
|
38
|
|
|
use TYPO3\CMS\Core\Resource\StorageRepository; |
|
39
|
|
|
use TYPO3\CMS\Core\SysLog\Action as SystemLogGenericAction; |
|
40
|
|
|
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification; |
|
41
|
|
|
use TYPO3\CMS\Core\SysLog\Type; |
|
42
|
|
|
use TYPO3\CMS\Core\SysLog\Type as SystemLogType; |
|
43
|
|
|
use TYPO3\CMS\Core\Type\Bitmask\BackendGroupMountOption; |
|
44
|
|
|
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation; |
|
45
|
|
|
use TYPO3\CMS\Core\Type\Bitmask\Permission; |
|
46
|
|
|
use TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException; |
|
47
|
|
|
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; |
|
48
|
|
|
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; |
|
49
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
|
50
|
|
|
use TYPO3\CMS\Core\Utility\StringUtility; |
|
51
|
|
|
use TYPO3\CMS\Core\Versioning\VersionState; |
|
52
|
|
|
use TYPO3\CMS\Install\Service\SessionService; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* TYPO3 backend user authentication |
|
56
|
|
|
* Contains most of the functions used for checking permissions, authenticating users, |
|
57
|
|
|
* setting up the user, and API for user from outside. |
|
58
|
|
|
* This class contains the configuration of the database fields used plus some |
|
59
|
|
|
* functions for the authentication process of backend users. |
|
60
|
|
|
*/ |
|
61
|
|
|
class BackendUserAuthentication extends AbstractUserAuthentication |
|
62
|
|
|
{ |
|
63
|
|
|
public const ROLE_SYSTEMMAINTAINER = 'systemMaintainer'; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* Should be set to the usergroup-column (id-list) in the user-record |
|
67
|
|
|
* @var string |
|
68
|
|
|
*/ |
|
69
|
|
|
public $usergroup_column = 'usergroup'; |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* The name of the group-table |
|
73
|
|
|
* @var string |
|
74
|
|
|
*/ |
|
75
|
|
|
public $usergroup_table = 'be_groups'; |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* holds lists of eg. tables, fields and other values related to the permission-system. See fetchGroupData |
|
79
|
|
|
* @var array |
|
80
|
|
|
* @internal |
|
81
|
|
|
*/ |
|
82
|
|
|
public $groupData = [ |
|
83
|
|
|
'allowed_languages' => '', |
|
84
|
|
|
'tables_select' => '', |
|
85
|
|
|
'tables_modify' => '', |
|
86
|
|
|
'pagetypes_select' => '', |
|
87
|
|
|
'non_exclude_fields' => '', |
|
88
|
|
|
'explicit_allowdeny' => '', |
|
89
|
|
|
'custom_options' => '', |
|
90
|
|
|
'file_permissions' => '', |
|
91
|
|
|
]; |
|
92
|
|
|
|
|
93
|
|
|
/** |
|
94
|
|
|
* This array holds the uid's of the groups in the listed order |
|
95
|
|
|
* @var array |
|
96
|
|
|
*/ |
|
97
|
|
|
public $userGroupsUID = []; |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* User workspace. |
|
101
|
|
|
* -99 is ERROR (none available) |
|
102
|
|
|
* 0 is online |
|
103
|
|
|
* >0 is custom workspaces |
|
104
|
|
|
* @var int |
|
105
|
|
|
*/ |
|
106
|
|
|
public $workspace = -99; |
|
107
|
|
|
|
|
108
|
|
|
/** |
|
109
|
|
|
* Custom workspace record if any |
|
110
|
|
|
* @var array |
|
111
|
|
|
*/ |
|
112
|
|
|
public $workspaceRec = []; |
|
113
|
|
|
|
|
114
|
|
|
/** |
|
115
|
|
|
* @var array Parsed user TSconfig |
|
116
|
|
|
*/ |
|
117
|
|
|
protected $userTS = []; |
|
118
|
|
|
|
|
119
|
|
|
/** |
|
120
|
|
|
* @var bool True if the user TSconfig was parsed and needs to be cached. |
|
121
|
|
|
*/ |
|
122
|
|
|
protected $userTSUpdated = false; |
|
123
|
|
|
|
|
124
|
|
|
/** |
|
125
|
|
|
* Contains last error message |
|
126
|
|
|
* @internal should only be used from within TYPO3 Core |
|
127
|
|
|
* @var string |
|
128
|
|
|
*/ |
|
129
|
|
|
public $errorMsg = ''; |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* Cache for checkWorkspaceCurrent() |
|
133
|
|
|
* @var array|null |
|
134
|
|
|
*/ |
|
135
|
|
|
protected $checkWorkspaceCurrent_cache; |
|
136
|
|
|
|
|
137
|
|
|
/** |
|
138
|
|
|
* @var \TYPO3\CMS\Core\Resource\ResourceStorage[] |
|
139
|
|
|
*/ |
|
140
|
|
|
protected $fileStorages; |
|
141
|
|
|
|
|
142
|
|
|
/** |
|
143
|
|
|
* @var array |
|
144
|
|
|
*/ |
|
145
|
|
|
protected $filePermissions; |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* Table in database with user data |
|
149
|
|
|
* @var string |
|
150
|
|
|
*/ |
|
151
|
|
|
public $user_table = 'be_users'; |
|
152
|
|
|
|
|
153
|
|
|
/** |
|
154
|
|
|
* Column for login-name |
|
155
|
|
|
* @var string |
|
156
|
|
|
*/ |
|
157
|
|
|
public $username_column = 'username'; |
|
158
|
|
|
|
|
159
|
|
|
/** |
|
160
|
|
|
* Column for password |
|
161
|
|
|
* @var string |
|
162
|
|
|
*/ |
|
163
|
|
|
public $userident_column = 'password'; |
|
164
|
|
|
|
|
165
|
|
|
/** |
|
166
|
|
|
* Column for user-id |
|
167
|
|
|
* @var string |
|
168
|
|
|
*/ |
|
169
|
|
|
public $userid_column = 'uid'; |
|
170
|
|
|
|
|
171
|
|
|
/** |
|
172
|
|
|
* @var string |
|
173
|
|
|
*/ |
|
174
|
|
|
public $lastLogin_column = 'lastlogin'; |
|
175
|
|
|
|
|
176
|
|
|
/** |
|
177
|
|
|
* @var array |
|
178
|
|
|
*/ |
|
179
|
|
|
public $enablecolumns = [ |
|
180
|
|
|
'rootLevel' => 1, |
|
181
|
|
|
'deleted' => 'deleted', |
|
182
|
|
|
'disabled' => 'disable', |
|
183
|
|
|
'starttime' => 'starttime', |
|
184
|
|
|
'endtime' => 'endtime' |
|
185
|
|
|
]; |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* Form field with login-name |
|
189
|
|
|
* @var string |
|
190
|
|
|
*/ |
|
191
|
|
|
public $formfield_uname = 'username'; |
|
192
|
|
|
|
|
193
|
|
|
/** |
|
194
|
|
|
* Form field with password |
|
195
|
|
|
* @var string |
|
196
|
|
|
*/ |
|
197
|
|
|
public $formfield_uident = 'userident'; |
|
198
|
|
|
|
|
199
|
|
|
/** |
|
200
|
|
|
* Form field with status: *'login', 'logout' |
|
201
|
|
|
* @var string |
|
202
|
|
|
*/ |
|
203
|
|
|
public $formfield_status = 'login_status'; |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* Decides if the writelog() function is called at login and logout |
|
207
|
|
|
* @var bool |
|
208
|
|
|
*/ |
|
209
|
|
|
public $writeStdLog = true; |
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* If the writelog() functions is called if a login-attempt has be tried without success |
|
213
|
|
|
* @var bool |
|
214
|
|
|
*/ |
|
215
|
|
|
public $writeAttemptLog = true; |
|
216
|
|
|
|
|
217
|
|
|
/** |
|
218
|
|
|
* @var int |
|
219
|
|
|
* @internal should only be used from within TYPO3 Core |
|
220
|
|
|
*/ |
|
221
|
|
|
public $firstMainGroup = 0; |
|
222
|
|
|
|
|
223
|
|
|
/** |
|
224
|
|
|
* User Config |
|
225
|
|
|
* @var array|string |
|
226
|
|
|
*/ |
|
227
|
|
|
public $uc; |
|
228
|
|
|
|
|
229
|
|
|
/** |
|
230
|
|
|
* User Config Default values: |
|
231
|
|
|
* The array may contain other fields for configuration. |
|
232
|
|
|
* For this, see "setup" extension and "TSconfig" document (User TSconfig, "setup.[xxx]....") |
|
233
|
|
|
* Reserved keys for other storage of session data: |
|
234
|
|
|
* moduleData |
|
235
|
|
|
* moduleSessionID |
|
236
|
|
|
* @var array |
|
237
|
|
|
* @internal should only be used from within TYPO3 Core |
|
238
|
|
|
*/ |
|
239
|
|
|
public $uc_default = [ |
|
240
|
|
|
'interfaceSetup' => '', |
|
241
|
|
|
// serialized content that is used to store interface pane and menu positions. Set by the logout.php-script |
|
242
|
|
|
'moduleData' => [], |
|
243
|
|
|
// user-data for the modules |
|
244
|
|
|
'emailMeAtLogin' => 0, |
|
245
|
|
|
'titleLen' => 50, |
|
246
|
|
|
'edit_RTE' => '1', |
|
247
|
|
|
'edit_docModuleUpload' => '1', |
|
248
|
|
|
'resizeTextareas_MaxHeight' => 500, |
|
249
|
|
|
]; |
|
250
|
|
|
|
|
251
|
|
|
/** |
|
252
|
|
|
* Login type, used for services. |
|
253
|
|
|
* @var string |
|
254
|
|
|
*/ |
|
255
|
|
|
public $loginType = 'BE'; |
|
256
|
|
|
|
|
257
|
|
|
/** |
|
258
|
|
|
* Constructor |
|
259
|
|
|
*/ |
|
260
|
|
|
public function __construct() |
|
261
|
|
|
{ |
|
262
|
|
|
$this->name = self::getCookieName(); |
|
263
|
|
|
parent::__construct(); |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
/** |
|
267
|
|
|
* Returns TRUE if user is admin |
|
268
|
|
|
* Basically this function evaluates if the ->user[admin] field has bit 0 set. If so, user is admin. |
|
269
|
|
|
* |
|
270
|
|
|
* @return bool |
|
271
|
|
|
*/ |
|
272
|
|
|
public function isAdmin() |
|
273
|
|
|
{ |
|
274
|
|
|
return is_array($this->user) && ($this->user['admin'] & 1) == 1; |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
/** |
|
278
|
|
|
* Returns TRUE if the current user is a member of group $groupId |
|
279
|
|
|
* $groupId must be set. $this->userGroupsUID must contain groups |
|
280
|
|
|
* Will return TRUE also if the user is a member of a group through subgroups. |
|
281
|
|
|
* |
|
282
|
|
|
* @param int $groupId Group ID to look for in $this->userGroupsUID |
|
283
|
|
|
* @return bool |
|
284
|
|
|
* @internal should only be used from within TYPO3 Core, use Context API for quicker access |
|
285
|
|
|
*/ |
|
286
|
|
|
public function isMemberOfGroup($groupId) |
|
287
|
|
|
{ |
|
288
|
|
|
$groupId = (int)$groupId; |
|
289
|
|
|
if (!empty($this->userGroupsUID) && $groupId) { |
|
290
|
|
|
return in_array($groupId, $this->userGroupsUID, true); |
|
291
|
|
|
} |
|
292
|
|
|
return false; |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
/** |
|
296
|
|
|
* Checks if the permissions is granted based on a page-record ($row) and $perms (binary and'ed) |
|
297
|
|
|
* |
|
298
|
|
|
* Bits for permissions, see $perms variable: |
|
299
|
|
|
* |
|
300
|
|
|
* 1 - Show: See/Copy page and the pagecontent. |
|
301
|
|
|
* 2 - Edit page: Change/Move the page, eg. change title, startdate, hidden. |
|
302
|
|
|
* 4 - Delete page: Delete the page and pagecontent. |
|
303
|
|
|
* 8 - New pages: Create new pages under the page. |
|
304
|
|
|
* 16 - Edit pagecontent: Change/Add/Delete/Move pagecontent. |
|
305
|
|
|
* |
|
306
|
|
|
* @param array $row Is the pagerow for which the permissions is checked |
|
307
|
|
|
* @param int $perms Is the binary representation of the permission we are going to check. Every bit in this number represents a permission that must be set. See function explanation. |
|
308
|
|
|
* @return bool |
|
309
|
|
|
*/ |
|
310
|
|
|
public function doesUserHaveAccess($row, $perms) |
|
311
|
|
|
{ |
|
312
|
|
|
$userPerms = $this->calcPerms($row); |
|
313
|
|
|
return ($userPerms & $perms) == $perms; |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
|
/** |
|
317
|
|
|
* Checks if the page id or page record ($idOrRow) is found within the webmounts set up for the user. |
|
318
|
|
|
* This should ALWAYS be checked for any page id a user works with, whether it's about reading, writing or whatever. |
|
319
|
|
|
* The point is that this will add the security that a user can NEVER touch parts outside his mounted |
|
320
|
|
|
* pages in the page tree. This is otherwise possible if the raw page permissions allows for it. |
|
321
|
|
|
* So this security check just makes it easier to make safe user configurations. |
|
322
|
|
|
* If the user is admin then it returns "1" right away |
|
323
|
|
|
* Otherwise the function will return the uid of the webmount which was first found in the rootline of the input page $id |
|
324
|
|
|
* |
|
325
|
|
|
* @param int|array $idOrRow Page ID or full page record to check |
|
326
|
|
|
* @param string $readPerms Content of "->getPagePermsClause(1)" (read-permissions). If not set, they will be internally calculated (but if you have the correct value right away you can save that database lookup!) |
|
327
|
|
|
* @param bool|int $exitOnError If set, then the function will exit with an error message. |
|
328
|
|
|
* @throws \RuntimeException |
|
329
|
|
|
* @return int|null The page UID of a page in the rootline that matched a mount point |
|
330
|
|
|
*/ |
|
331
|
|
|
public function isInWebMount($idOrRow, $readPerms = '', $exitOnError = 0) |
|
332
|
|
|
{ |
|
333
|
|
|
if ($this->isAdmin()) { |
|
334
|
|
|
return 1; |
|
335
|
|
|
} |
|
336
|
|
|
$checkRec = []; |
|
337
|
|
|
$fetchPageFromDatabase = true; |
|
338
|
|
|
if (is_array($idOrRow)) { |
|
339
|
|
|
if (empty($idOrRow['uid'])) { |
|
340
|
|
|
throw new \RuntimeException('The given page record is invalid. Missing uid.', 1578950324); |
|
341
|
|
|
} |
|
342
|
|
|
$checkRec = $idOrRow; |
|
343
|
|
|
$id = (int)$idOrRow['uid']; |
|
344
|
|
|
// ensure the required fields are present on the record |
|
345
|
|
|
if (isset($checkRec['t3ver_oid'], $checkRec[$GLOBALS['TCA']['pages']['ctrl']['languageField']], $checkRec[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']])) { |
|
346
|
|
|
$fetchPageFromDatabase = false; |
|
347
|
|
|
} |
|
348
|
|
|
} else { |
|
349
|
|
|
$id = (int)$idOrRow; |
|
350
|
|
|
} |
|
351
|
|
|
if ($fetchPageFromDatabase) { |
|
352
|
|
|
// Check if input id is an offline version page in which case we will map id to the online version: |
|
353
|
|
|
$checkRec = BackendUtility::getRecord( |
|
354
|
|
|
'pages', |
|
355
|
|
|
$id, |
|
356
|
|
|
't3ver_oid,' |
|
357
|
|
|
. $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'] . ',' |
|
358
|
|
|
. $GLOBALS['TCA']['pages']['ctrl']['languageField'] |
|
359
|
|
|
); |
|
360
|
|
|
} |
|
361
|
|
|
if ($checkRec['t3ver_oid'] > 0) { |
|
362
|
|
|
$id = (int)$checkRec['t3ver_oid']; |
|
363
|
|
|
} |
|
364
|
|
|
// if current rec is a translation then get uid from l10n_parent instead |
|
365
|
|
|
// because web mounts point to pages in default language and rootline returns uids of default languages |
|
366
|
|
|
if ((int)$checkRec[$GLOBALS['TCA']['pages']['ctrl']['languageField']] !== 0 && (int)$checkRec[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] !== 0) { |
|
367
|
|
|
$id = (int)$checkRec[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']]; |
|
368
|
|
|
} |
|
369
|
|
|
if (!$readPerms) { |
|
370
|
|
|
$readPerms = $this->getPagePermsClause(Permission::PAGE_SHOW); |
|
371
|
|
|
} |
|
372
|
|
|
if ($id > 0) { |
|
373
|
|
|
$wM = $this->returnWebmounts(); |
|
374
|
|
|
$rL = BackendUtility::BEgetRootLine($id, ' AND ' . $readPerms, true); |
|
375
|
|
|
foreach ($rL as $v) { |
|
376
|
|
|
if ($v['uid'] && in_array($v['uid'], $wM)) { |
|
377
|
|
|
return $v['uid']; |
|
378
|
|
|
} |
|
379
|
|
|
} |
|
380
|
|
|
} |
|
381
|
|
|
if ($exitOnError) { |
|
382
|
|
|
throw new \RuntimeException('Access Error: This page is not within your DB-mounts', 1294586445); |
|
383
|
|
|
} |
|
384
|
|
|
return null; |
|
385
|
|
|
} |
|
386
|
|
|
|
|
387
|
|
|
/** |
|
388
|
|
|
* Checks access to a backend module with the $MCONF passed as first argument |
|
389
|
|
|
* |
|
390
|
|
|
* @param array $conf $MCONF array of a backend module! |
|
391
|
|
|
* @throws \RuntimeException |
|
392
|
|
|
* @return bool Will return TRUE if $MCONF['access'] is not set at all, if the BE_USER is admin or if the module is enabled in the be_users/be_groups records of the user (specifically enabled). Will return FALSE if the module name is not even found in $TBE_MODULES |
|
393
|
|
|
*/ |
|
394
|
|
|
public function modAccess($conf) |
|
395
|
|
|
{ |
|
396
|
|
|
if (!BackendUtility::isModuleSetInTBE_MODULES($conf['name'])) { |
|
397
|
|
|
throw new \RuntimeException('Fatal Error: This module "' . $conf['name'] . '" is not enabled in TBE_MODULES', 1294586446); |
|
398
|
|
|
} |
|
399
|
|
|
// Workspaces check: |
|
400
|
|
|
if ( |
|
401
|
|
|
!empty($conf['workspaces']) |
|
402
|
|
|
&& ExtensionManagementUtility::isLoaded('workspaces') |
|
403
|
|
|
&& ($this->workspace !== 0 || !GeneralUtility::inList($conf['workspaces'], 'online')) |
|
404
|
|
|
&& ($this->workspace <= 0 || !GeneralUtility::inList($conf['workspaces'], 'custom')) |
|
405
|
|
|
) { |
|
406
|
|
|
throw new \RuntimeException('Workspace Error: This module "' . $conf['name'] . '" is not available under the current workspace', 1294586447); |
|
407
|
|
|
} |
|
408
|
|
|
// Returns false if conf[access] is set to system maintainers and the user is system maintainer |
|
409
|
|
|
if (strpos($conf['access'], self::ROLE_SYSTEMMAINTAINER) !== false && !$this->isSystemMaintainer()) { |
|
410
|
|
|
throw new \RuntimeException('This module "' . $conf['name'] . '" is only available as system maintainer', 1504804727); |
|
411
|
|
|
} |
|
412
|
|
|
// Returns TRUE if conf[access] is not set at all or if the user is admin |
|
413
|
|
|
if (!$conf['access'] || $this->isAdmin()) { |
|
414
|
|
|
return true; |
|
415
|
|
|
} |
|
416
|
|
|
// If $conf['access'] is set but not with 'admin' then we return TRUE, if the module is found in the modList |
|
417
|
|
|
$acs = false; |
|
418
|
|
|
if (strpos($conf['access'], 'admin') === false && $conf['name']) { |
|
419
|
|
|
$acs = $this->check('modules', $conf['name']); |
|
420
|
|
|
} |
|
421
|
|
|
if (!$acs) { |
|
422
|
|
|
throw new \RuntimeException('Access Error: You don\'t have access to this module.', 1294586448); |
|
423
|
|
|
} |
|
424
|
|
|
return $acs; |
|
425
|
|
|
} |
|
426
|
|
|
|
|
427
|
|
|
/** |
|
428
|
|
|
* Checks if the user is in the valid list of allowed system maintainers. if the list is not set, |
|
429
|
|
|
* then all admins are system maintainers. If the list is empty, no one is system maintainer (good for production |
|
430
|
|
|
* systems). If the currently logged in user is in "switch user" mode, this method will return false. |
|
431
|
|
|
* |
|
432
|
|
|
* @return bool |
|
433
|
|
|
*/ |
|
434
|
|
|
public function isSystemMaintainer(): bool |
|
435
|
|
|
{ |
|
436
|
|
|
if (!$this->isAdmin()) { |
|
437
|
|
|
return false; |
|
438
|
|
|
} |
|
439
|
|
|
|
|
440
|
|
|
if ($GLOBALS['BE_USER']->getOriginalUserIdWhenInSwitchUserMode()) { |
|
441
|
|
|
return false; |
|
442
|
|
|
} |
|
443
|
|
|
if (Environment::getContext()->isDevelopment()) { |
|
444
|
|
|
return true; |
|
445
|
|
|
} |
|
446
|
|
|
$systemMaintainers = $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? []; |
|
447
|
|
|
$systemMaintainers = array_map('intval', $systemMaintainers); |
|
448
|
|
|
if (!empty($systemMaintainers)) { |
|
449
|
|
|
return in_array((int)$this->user['uid'], $systemMaintainers, true); |
|
450
|
|
|
} |
|
451
|
|
|
// No system maintainers set up yet, so any admin is allowed to access the modules |
|
452
|
|
|
// but explicitly no system maintainers allowed (empty string in TYPO3_CONF_VARS). |
|
453
|
|
|
// @todo: this needs to be adjusted once system maintainers can log into the install tool with their credentials |
|
454
|
|
|
if (isset($GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers']) |
|
455
|
|
|
&& empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'])) { |
|
456
|
|
|
return false; |
|
457
|
|
|
} |
|
458
|
|
|
return true; |
|
459
|
|
|
} |
|
460
|
|
|
|
|
461
|
|
|
/** |
|
462
|
|
|
* Returns a WHERE-clause for the pages-table where user permissions according to input argument, $perms, is validated. |
|
463
|
|
|
* $perms is the "mask" used to select. Fx. if $perms is 1 then you'll get all pages that a user can actually see! |
|
464
|
|
|
* 2^0 = show (1) |
|
465
|
|
|
* 2^1 = edit (2) |
|
466
|
|
|
* 2^2 = delete (4) |
|
467
|
|
|
* 2^3 = new (8) |
|
468
|
|
|
* If the user is 'admin' " 1=1" is returned (no effect) |
|
469
|
|
|
* If the user is not set at all (->user is not an array), then " 1=0" is returned (will cause no selection results at all) |
|
470
|
|
|
* The 95% use of this function is "->getPagePermsClause(1)" which will |
|
471
|
|
|
* return WHERE clauses for *selecting* pages in backend listings - in other words this will check read permissions. |
|
472
|
|
|
* |
|
473
|
|
|
* @param int $perms Permission mask to use, see function description |
|
474
|
|
|
* @return string Part of where clause. Prefix " AND " to this. |
|
475
|
|
|
* @internal should only be used from within TYPO3 Core, use PagePermissionDatabaseRestriction instead. |
|
476
|
|
|
*/ |
|
477
|
|
|
public function getPagePermsClause($perms) |
|
478
|
|
|
{ |
|
479
|
|
|
if (is_array($this->user)) { |
|
480
|
|
|
if ($this->isAdmin()) { |
|
481
|
|
|
return ' 1=1'; |
|
482
|
|
|
} |
|
483
|
|
|
// Make sure it's integer. |
|
484
|
|
|
$perms = (int)$perms; |
|
485
|
|
|
$expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class) |
|
486
|
|
|
->getQueryBuilderForTable('pages') |
|
487
|
|
|
->expr(); |
|
488
|
|
|
|
|
489
|
|
|
// User |
|
490
|
|
|
$constraint = $expressionBuilder->orX( |
|
491
|
|
|
$expressionBuilder->comparison( |
|
492
|
|
|
$expressionBuilder->bitAnd('pages.perms_everybody', $perms), |
|
493
|
|
|
ExpressionBuilder::EQ, |
|
494
|
|
|
$perms |
|
495
|
|
|
), |
|
496
|
|
|
$expressionBuilder->andX( |
|
497
|
|
|
$expressionBuilder->eq('pages.perms_userid', (int)$this->user['uid']), |
|
498
|
|
|
$expressionBuilder->comparison( |
|
499
|
|
|
$expressionBuilder->bitAnd('pages.perms_user', $perms), |
|
500
|
|
|
ExpressionBuilder::EQ, |
|
501
|
|
|
$perms |
|
502
|
|
|
) |
|
503
|
|
|
) |
|
504
|
|
|
); |
|
505
|
|
|
|
|
506
|
|
|
// Group (if any is set) |
|
507
|
|
|
if (!empty($this->userGroupsUID)) { |
|
508
|
|
|
$constraint->add( |
|
509
|
|
|
$expressionBuilder->andX( |
|
510
|
|
|
$expressionBuilder->in( |
|
511
|
|
|
'pages.perms_groupid', |
|
512
|
|
|
$this->userGroupsUID |
|
513
|
|
|
), |
|
514
|
|
|
$expressionBuilder->comparison( |
|
515
|
|
|
$expressionBuilder->bitAnd('pages.perms_group', $perms), |
|
516
|
|
|
ExpressionBuilder::EQ, |
|
517
|
|
|
$perms |
|
518
|
|
|
) |
|
519
|
|
|
) |
|
520
|
|
|
); |
|
521
|
|
|
} |
|
522
|
|
|
|
|
523
|
|
|
$constraint = ' (' . (string)$constraint . ')'; |
|
524
|
|
|
|
|
525
|
|
|
// **************** |
|
526
|
|
|
// getPagePermsClause-HOOK |
|
527
|
|
|
// **************** |
|
528
|
|
|
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getPagePermsClause'] ?? [] as $_funcRef) { |
|
529
|
|
|
$_params = ['currentClause' => $constraint, 'perms' => $perms]; |
|
530
|
|
|
$constraint = GeneralUtility::callUserFunction($_funcRef, $_params, $this); |
|
531
|
|
|
} |
|
532
|
|
|
return $constraint; |
|
533
|
|
|
} |
|
534
|
|
|
return ' 1=0'; |
|
535
|
|
|
} |
|
536
|
|
|
|
|
537
|
|
|
/** |
|
538
|
|
|
* Returns a combined binary representation of the current users permissions for the page-record, $row. |
|
539
|
|
|
* The perms for user, group and everybody is OR'ed together (provided that the page-owner is the user |
|
540
|
|
|
* and for the groups that the user is a member of the group. |
|
541
|
|
|
* If the user is admin, 31 is returned (full permissions for all five flags) |
|
542
|
|
|
* |
|
543
|
|
|
* @param array $row Input page row with all perms_* fields available. |
|
544
|
|
|
* @return int Bitwise representation of the users permissions in relation to input page row, $row |
|
545
|
|
|
*/ |
|
546
|
|
|
public function calcPerms($row) |
|
547
|
|
|
{ |
|
548
|
|
|
// Return 31 for admin users. |
|
549
|
|
|
if ($this->isAdmin()) { |
|
550
|
|
|
return Permission::ALL; |
|
551
|
|
|
} |
|
552
|
|
|
// Return 0 if page is not within the allowed web mount |
|
553
|
|
|
if (!$this->isInWebMount($row)) { |
|
|
|
|
|
|
554
|
|
|
return Permission::NOTHING; |
|
555
|
|
|
} |
|
556
|
|
|
$out = Permission::NOTHING; |
|
557
|
|
|
if ( |
|
558
|
|
|
isset($row['perms_userid']) && isset($row['perms_user']) && isset($row['perms_groupid']) |
|
559
|
|
|
&& isset($row['perms_group']) && isset($row['perms_everybody']) && !empty($this->userGroupsUID) |
|
560
|
|
|
) { |
|
561
|
|
|
if ($this->user['uid'] == $row['perms_userid']) { |
|
562
|
|
|
$out |= $row['perms_user']; |
|
563
|
|
|
} |
|
564
|
|
|
if ($this->isMemberOfGroup($row['perms_groupid'])) { |
|
565
|
|
|
$out |= $row['perms_group']; |
|
566
|
|
|
} |
|
567
|
|
|
$out |= $row['perms_everybody']; |
|
568
|
|
|
} |
|
569
|
|
|
// **************** |
|
570
|
|
|
// CALCPERMS hook |
|
571
|
|
|
// **************** |
|
572
|
|
|
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['calcPerms'] ?? [] as $_funcRef) { |
|
573
|
|
|
$_params = [ |
|
574
|
|
|
'row' => $row, |
|
575
|
|
|
'outputPermissions' => $out |
|
576
|
|
|
]; |
|
577
|
|
|
$out = GeneralUtility::callUserFunction($_funcRef, $_params, $this); |
|
578
|
|
|
} |
|
579
|
|
|
return $out; |
|
580
|
|
|
} |
|
581
|
|
|
|
|
582
|
|
|
/** |
|
583
|
|
|
* Returns TRUE if the RTE (Rich Text Editor) is enabled for the user. |
|
584
|
|
|
* |
|
585
|
|
|
* @return bool |
|
586
|
|
|
* @internal should only be used from within TYPO3 Core |
|
587
|
|
|
*/ |
|
588
|
|
|
public function isRTE() |
|
589
|
|
|
{ |
|
590
|
|
|
return (bool)$this->uc['edit_RTE']; |
|
591
|
|
|
} |
|
592
|
|
|
|
|
593
|
|
|
/** |
|
594
|
|
|
* Returns TRUE if the $value is found in the list in a $this->groupData[] index pointed to by $type (array key). |
|
595
|
|
|
* Can thus be users to check for modules, exclude-fields, select/modify permissions for tables etc. |
|
596
|
|
|
* If user is admin TRUE is also returned |
|
597
|
|
|
* Please see the document Inside TYPO3 for examples. |
|
598
|
|
|
* |
|
599
|
|
|
* @param string $type The type value; "webmounts", "filemounts", "pagetypes_select", "tables_select", "tables_modify", "non_exclude_fields", "modules", "available_widgets", "mfa_providers" |
|
600
|
|
|
* @param string $value String to search for in the groupData-list |
|
601
|
|
|
* @return bool TRUE if permission is granted (that is, the value was found in the groupData list - or the BE_USER is "admin") |
|
602
|
|
|
*/ |
|
603
|
|
|
public function check($type, $value) |
|
604
|
|
|
{ |
|
605
|
|
|
return isset($this->groupData[$type]) |
|
606
|
|
|
&& ($this->isAdmin() || GeneralUtility::inList($this->groupData[$type], $value)); |
|
607
|
|
|
} |
|
608
|
|
|
|
|
609
|
|
|
/** |
|
610
|
|
|
* Checking the authMode of a select field with authMode set |
|
611
|
|
|
* |
|
612
|
|
|
* @param string $table Table name |
|
613
|
|
|
* @param string $field Field name (must be configured in TCA and of type "select" with authMode set!) |
|
614
|
|
|
* @param string $value Value to evaluation (single value, must not contain any of the chars ":,|") |
|
615
|
|
|
* @param string $authMode Auth mode keyword (explicitAllow, explicitDeny, individual) |
|
616
|
|
|
* @return bool Whether access is granted or not |
|
617
|
|
|
*/ |
|
618
|
|
|
public function checkAuthMode($table, $field, $value, $authMode) |
|
619
|
|
|
{ |
|
620
|
|
|
// Admin users can do anything: |
|
621
|
|
|
if ($this->isAdmin()) { |
|
622
|
|
|
return true; |
|
623
|
|
|
} |
|
624
|
|
|
// Allow all blank values: |
|
625
|
|
|
if ((string)$value === '') { |
|
626
|
|
|
return true; |
|
627
|
|
|
} |
|
628
|
|
|
// Allow dividers: |
|
629
|
|
|
if ($value === '--div--') { |
|
630
|
|
|
return true; |
|
631
|
|
|
} |
|
632
|
|
|
// Certain characters are not allowed in the value |
|
633
|
|
|
if (preg_match('/[:|,]/', $value)) { |
|
634
|
|
|
return false; |
|
635
|
|
|
} |
|
636
|
|
|
// Initialize: |
|
637
|
|
|
$testValue = $table . ':' . $field . ':' . $value; |
|
638
|
|
|
$out = true; |
|
639
|
|
|
// Checking value: |
|
640
|
|
|
switch ((string)$authMode) { |
|
641
|
|
|
case 'explicitAllow': |
|
642
|
|
|
if (!GeneralUtility::inList($this->groupData['explicit_allowdeny'], $testValue . ':ALLOW')) { |
|
643
|
|
|
$out = false; |
|
644
|
|
|
} |
|
645
|
|
|
break; |
|
646
|
|
|
case 'explicitDeny': |
|
647
|
|
|
if (GeneralUtility::inList($this->groupData['explicit_allowdeny'], $testValue . ':DENY')) { |
|
648
|
|
|
$out = false; |
|
649
|
|
|
} |
|
650
|
|
|
break; |
|
651
|
|
|
case 'individual': |
|
652
|
|
|
if (is_array($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]['columns'][$field])) { |
|
653
|
|
|
$items = $GLOBALS['TCA'][$table]['columns'][$field]['config']['items']; |
|
654
|
|
|
if (is_array($items)) { |
|
655
|
|
|
foreach ($items as $iCfg) { |
|
656
|
|
|
if ((string)$iCfg[1] === (string)$value && $iCfg[4]) { |
|
657
|
|
|
switch ((string)$iCfg[4]) { |
|
658
|
|
|
case 'EXPL_ALLOW': |
|
659
|
|
|
if (!GeneralUtility::inList( |
|
660
|
|
|
$this->groupData['explicit_allowdeny'], |
|
661
|
|
|
$testValue . ':ALLOW' |
|
662
|
|
|
)) { |
|
663
|
|
|
$out = false; |
|
664
|
|
|
} |
|
665
|
|
|
break; |
|
666
|
|
|
case 'EXPL_DENY': |
|
667
|
|
|
if (GeneralUtility::inList($this->groupData['explicit_allowdeny'], $testValue . ':DENY')) { |
|
668
|
|
|
$out = false; |
|
669
|
|
|
} |
|
670
|
|
|
break; |
|
671
|
|
|
} |
|
672
|
|
|
break; |
|
673
|
|
|
} |
|
674
|
|
|
} |
|
675
|
|
|
} |
|
676
|
|
|
} |
|
677
|
|
|
break; |
|
678
|
|
|
} |
|
679
|
|
|
return $out; |
|
680
|
|
|
} |
|
681
|
|
|
|
|
682
|
|
|
/** |
|
683
|
|
|
* Checking if a language value (-1, 0 and >0 for sys_language records) is allowed to be edited by the user. |
|
684
|
|
|
* |
|
685
|
|
|
* @param int $langValue Language value to evaluate |
|
686
|
|
|
* @return bool Returns TRUE if the language value is allowed, otherwise FALSE. |
|
687
|
|
|
*/ |
|
688
|
|
|
public function checkLanguageAccess($langValue) |
|
689
|
|
|
{ |
|
690
|
|
|
// The users language list must be non-blank - otherwise all languages are allowed. |
|
691
|
|
|
if (trim($this->groupData['allowed_languages']) !== '') { |
|
692
|
|
|
$langValue = (int)$langValue; |
|
693
|
|
|
// Language must either be explicitly allowed OR the lang Value be "-1" (all languages) |
|
694
|
|
|
if ($langValue != -1 && !$this->check('allowed_languages', (string)$langValue)) { |
|
695
|
|
|
return false; |
|
696
|
|
|
} |
|
697
|
|
|
} |
|
698
|
|
|
return true; |
|
699
|
|
|
} |
|
700
|
|
|
|
|
701
|
|
|
/** |
|
702
|
|
|
* Check if user has access to all existing localizations for a certain record |
|
703
|
|
|
* |
|
704
|
|
|
* @param string $table The table |
|
705
|
|
|
* @param array $record The current record |
|
706
|
|
|
* @return bool |
|
707
|
|
|
*/ |
|
708
|
|
|
public function checkFullLanguagesAccess($table, $record) |
|
709
|
|
|
{ |
|
710
|
|
|
if (!$this->checkLanguageAccess(0)) { |
|
711
|
|
|
return false; |
|
712
|
|
|
} |
|
713
|
|
|
|
|
714
|
|
|
if (BackendUtility::isTableLocalizable($table)) { |
|
715
|
|
|
$pointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']; |
|
716
|
|
|
$pointerValue = $record[$pointerField] > 0 ? $record[$pointerField] : $record['uid']; |
|
717
|
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); |
|
718
|
|
|
$queryBuilder->getRestrictions() |
|
719
|
|
|
->removeAll() |
|
720
|
|
|
->add(GeneralUtility::makeInstance(DeletedRestriction::class)) |
|
721
|
|
|
->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, (int)$this->workspace)); |
|
722
|
|
|
$recordLocalizations = $queryBuilder->select('*') |
|
723
|
|
|
->from($table) |
|
724
|
|
|
->where( |
|
725
|
|
|
$queryBuilder->expr()->eq( |
|
726
|
|
|
$pointerField, |
|
727
|
|
|
$queryBuilder->createNamedParameter($pointerValue, \PDO::PARAM_INT) |
|
728
|
|
|
) |
|
729
|
|
|
) |
|
730
|
|
|
->execute() |
|
731
|
|
|
->fetchAll(); |
|
732
|
|
|
|
|
733
|
|
|
foreach ($recordLocalizations as $recordLocalization) { |
|
734
|
|
|
if (!$this->checkLanguageAccess($recordLocalization[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) { |
|
735
|
|
|
return false; |
|
736
|
|
|
} |
|
737
|
|
|
} |
|
738
|
|
|
} |
|
739
|
|
|
return true; |
|
740
|
|
|
} |
|
741
|
|
|
|
|
742
|
|
|
/** |
|
743
|
|
|
* Checking if a user has editing access to a record from a $GLOBALS['TCA'] table. |
|
744
|
|
|
* The checks does not take page permissions and other "environmental" things into account. |
|
745
|
|
|
* It only deal with record internals; If any values in the record fields disallows it. |
|
746
|
|
|
* For instance languages settings, authMode selector boxes are evaluated (and maybe more in the future). |
|
747
|
|
|
* It will check for workspace dependent access. |
|
748
|
|
|
* The function takes an ID (int) or row (array) as second argument. |
|
749
|
|
|
* |
|
750
|
|
|
* @param string $table Table name |
|
751
|
|
|
* @param int|array $idOrRow If integer, then this is the ID of the record. If Array this just represents fields in the record. |
|
752
|
|
|
* @param bool $newRecord Set, if testing a new (non-existing) record array. Will disable certain checks that doesn't make much sense in that context. |
|
753
|
|
|
* @param bool $deletedRecord Set, if testing a deleted record array. |
|
754
|
|
|
* @param bool $checkFullLanguageAccess Set, whenever access to all translations of the record is required |
|
755
|
|
|
* @return bool TRUE if OK, otherwise FALSE |
|
756
|
|
|
* @internal should only be used from within TYPO3 Core |
|
757
|
|
|
*/ |
|
758
|
|
|
public function recordEditAccessInternals($table, $idOrRow, $newRecord = false, $deletedRecord = false, $checkFullLanguageAccess = false): bool |
|
759
|
|
|
{ |
|
760
|
|
|
if (!isset($GLOBALS['TCA'][$table])) { |
|
761
|
|
|
return false; |
|
762
|
|
|
} |
|
763
|
|
|
// Always return TRUE for Admin users. |
|
764
|
|
|
if ($this->isAdmin()) { |
|
765
|
|
|
return true; |
|
766
|
|
|
} |
|
767
|
|
|
// Fetching the record if the $idOrRow variable was not an array on input: |
|
768
|
|
|
if (!is_array($idOrRow)) { |
|
769
|
|
|
if ($deletedRecord) { |
|
770
|
|
|
$idOrRow = BackendUtility::getRecord($table, $idOrRow, '*', '', false); |
|
771
|
|
|
} else { |
|
772
|
|
|
$idOrRow = BackendUtility::getRecord($table, $idOrRow); |
|
773
|
|
|
} |
|
774
|
|
|
if (!is_array($idOrRow)) { |
|
775
|
|
|
$this->errorMsg = 'ERROR: Record could not be fetched.'; |
|
776
|
|
|
return false; |
|
777
|
|
|
} |
|
778
|
|
|
} |
|
779
|
|
|
// Checking languages: |
|
780
|
|
|
if ($table === 'pages' && $checkFullLanguageAccess && !$this->checkFullLanguagesAccess($table, $idOrRow)) { |
|
781
|
|
|
return false; |
|
782
|
|
|
} |
|
783
|
|
|
if ($GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? false) { |
|
784
|
|
|
// Language field must be found in input row - otherwise it does not make sense. |
|
785
|
|
|
if (isset($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) { |
|
786
|
|
|
if (!$this->checkLanguageAccess($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']])) { |
|
787
|
|
|
$this->errorMsg = 'ERROR: Language was not allowed.'; |
|
788
|
|
|
return false; |
|
789
|
|
|
} |
|
790
|
|
|
if ( |
|
791
|
|
|
$checkFullLanguageAccess && $idOrRow[$GLOBALS['TCA'][$table]['ctrl']['languageField']] == 0 |
|
792
|
|
|
&& !$this->checkFullLanguagesAccess($table, $idOrRow) |
|
793
|
|
|
) { |
|
794
|
|
|
$this->errorMsg = 'ERROR: Related/affected language was not allowed.'; |
|
795
|
|
|
return false; |
|
796
|
|
|
} |
|
797
|
|
|
} else { |
|
798
|
|
|
$this->errorMsg = 'ERROR: The "languageField" field named "' |
|
799
|
|
|
. $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '" was not found in testing record!'; |
|
800
|
|
|
return false; |
|
801
|
|
|
} |
|
802
|
|
|
} |
|
803
|
|
|
// Checking authMode fields: |
|
804
|
|
|
if (is_array($GLOBALS['TCA'][$table]['columns'])) { |
|
805
|
|
|
foreach ($GLOBALS['TCA'][$table]['columns'] as $fieldName => $fieldValue) { |
|
806
|
|
|
if (isset($idOrRow[$fieldName]) |
|
807
|
|
|
&& ($fieldValue['config']['type'] ?? '') === 'select' |
|
808
|
|
|
&& ($fieldValue['config']['authMode'] ?? false) |
|
809
|
|
|
&& ($fieldValue['config']['authMode_enforce'] ?? '') === 'strict' |
|
810
|
|
|
&& !$this->checkAuthMode($table, $fieldName, $idOrRow[$fieldName], $fieldValue['config']['authMode'])) { |
|
811
|
|
|
$this->errorMsg = 'ERROR: authMode "' . $fieldValue['config']['authMode'] |
|
812
|
|
|
. '" failed for field "' . $fieldName . '" with value "' |
|
813
|
|
|
. $idOrRow[$fieldName] . '" evaluated'; |
|
814
|
|
|
return false; |
|
815
|
|
|
} |
|
816
|
|
|
} |
|
817
|
|
|
} |
|
818
|
|
|
// Checking "editlock" feature (doesn't apply to new records) |
|
819
|
|
|
if (!$newRecord && ($GLOBALS['TCA'][$table]['ctrl']['editlock'] ?? false)) { |
|
820
|
|
|
if (isset($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['editlock']])) { |
|
821
|
|
|
if ($idOrRow[$GLOBALS['TCA'][$table]['ctrl']['editlock']]) { |
|
822
|
|
|
$this->errorMsg = 'ERROR: Record was locked for editing. Only admin users can change this state.'; |
|
823
|
|
|
return false; |
|
824
|
|
|
} |
|
825
|
|
|
} else { |
|
826
|
|
|
$this->errorMsg = 'ERROR: The "editLock" field named "' . $GLOBALS['TCA'][$table]['ctrl']['editlock'] |
|
827
|
|
|
. '" was not found in testing record!'; |
|
828
|
|
|
return false; |
|
829
|
|
|
} |
|
830
|
|
|
} |
|
831
|
|
|
// Checking record permissions |
|
832
|
|
|
// THIS is where we can include a check for "perms_" fields for other records than pages... |
|
833
|
|
|
// Process any hooks |
|
834
|
|
|
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['recordEditAccessInternals'] ?? [] as $funcRef) { |
|
835
|
|
|
$params = [ |
|
836
|
|
|
'table' => $table, |
|
837
|
|
|
'idOrRow' => $idOrRow, |
|
838
|
|
|
'newRecord' => $newRecord |
|
839
|
|
|
]; |
|
840
|
|
|
if (!GeneralUtility::callUserFunction($funcRef, $params, $this)) { |
|
841
|
|
|
return false; |
|
842
|
|
|
} |
|
843
|
|
|
} |
|
844
|
|
|
// Finally, return TRUE if all is well. |
|
845
|
|
|
return true; |
|
846
|
|
|
} |
|
847
|
|
|
|
|
848
|
|
|
/** |
|
849
|
|
|
* Returns TRUE if the BE_USER is allowed to *create* shortcuts in the backend modules |
|
850
|
|
|
* |
|
851
|
|
|
* @return bool |
|
852
|
|
|
*/ |
|
853
|
|
|
public function mayMakeShortcut() |
|
854
|
|
|
{ |
|
855
|
|
|
return ($this->getTSConfig()['options.']['enableBookmarks'] ?? false) |
|
856
|
|
|
&& !($this->getTSConfig()['options.']['mayNotCreateEditBookmarks'] ?? false); |
|
857
|
|
|
} |
|
858
|
|
|
|
|
859
|
|
|
/** |
|
860
|
|
|
* Checking if editing of an existing record is allowed in current workspace if that is offline. |
|
861
|
|
|
* Rules for editing in offline mode: |
|
862
|
|
|
* - record supports versioning and is an offline version from workspace and has the current stage |
|
863
|
|
|
* - or record (any) is in a branch where there is a page which is a version from the workspace |
|
864
|
|
|
* and where the stage is not preventing records |
|
865
|
|
|
* |
|
866
|
|
|
* @param string $table Table of record |
|
867
|
|
|
* @param array|int $recData Integer (record uid) or array where fields are at least: pid, t3ver_wsid, t3ver_oid, t3ver_stage (if versioningWS is set) |
|
868
|
|
|
* @return string String error code, telling the failure state. FALSE=All ok |
|
869
|
|
|
* @internal should only be used from within TYPO3 Core |
|
870
|
|
|
*/ |
|
871
|
|
|
public function workspaceCannotEditRecord($table, $recData) |
|
872
|
|
|
{ |
|
873
|
|
|
// Only test if the user is in a workspace |
|
874
|
|
|
if ($this->workspace === 0) { |
|
875
|
|
|
return false; |
|
|
|
|
|
|
876
|
|
|
} |
|
877
|
|
|
$tableSupportsVersioning = BackendUtility::isTableWorkspaceEnabled($table); |
|
878
|
|
|
if (!is_array($recData)) { |
|
879
|
|
|
$recData = BackendUtility::getRecord( |
|
880
|
|
|
$table, |
|
881
|
|
|
$recData, |
|
882
|
|
|
'pid' . ($tableSupportsVersioning ? ',t3ver_oid,t3ver_wsid,t3ver_state,t3ver_stage' : '') |
|
883
|
|
|
); |
|
884
|
|
|
} |
|
885
|
|
|
if (is_array($recData)) { |
|
886
|
|
|
// We are testing a "version" (identified by having a t3ver_oid): it can be edited provided |
|
887
|
|
|
// that workspace matches and versioning is enabled for the table. |
|
888
|
|
|
$versionState = new VersionState($recData['t3ver_state'] ?? 0); |
|
889
|
|
|
if ($tableSupportsVersioning |
|
890
|
|
|
&& ( |
|
891
|
|
|
$versionState->equals(VersionState::NEW_PLACEHOLDER) || (int)(($recData['t3ver_oid'] ?? 0) > 0) |
|
892
|
|
|
) |
|
893
|
|
|
) { |
|
894
|
|
|
if ((int)$recData['t3ver_wsid'] !== $this->workspace) { |
|
895
|
|
|
// So does workspace match? |
|
896
|
|
|
return 'Workspace ID of record didn\'t match current workspace'; |
|
897
|
|
|
} |
|
898
|
|
|
// So is the user allowed to "use" the edit stage within the workspace? |
|
899
|
|
|
return $this->workspaceCheckStageForCurrent(0) |
|
|
|
|
|
|
900
|
|
|
? false |
|
901
|
|
|
: 'User\'s access level did not allow for editing'; |
|
902
|
|
|
} |
|
903
|
|
|
// Check if we are testing a "live" record |
|
904
|
|
|
if ($this->workspaceAllowsLiveEditingInTable($table)) { |
|
905
|
|
|
// Live records are OK in the current workspace |
|
906
|
|
|
return false; |
|
|
|
|
|
|
907
|
|
|
} |
|
908
|
|
|
// If not offline, output error |
|
909
|
|
|
return 'Online record was not in a workspace!'; |
|
910
|
|
|
} |
|
911
|
|
|
return 'No record'; |
|
912
|
|
|
} |
|
913
|
|
|
|
|
914
|
|
|
/** |
|
915
|
|
|
* Evaluates if a user is allowed to edit the offline version |
|
916
|
|
|
* |
|
917
|
|
|
* @param string $table Table of record |
|
918
|
|
|
* @param array|int $recData Integer (record uid) or array where fields are at least: pid, t3ver_wsid, t3ver_stage (if versioningWS is set) |
|
919
|
|
|
* @return string String error code, telling the failure state. FALSE=All ok |
|
920
|
|
|
* @see workspaceCannotEditRecord() |
|
921
|
|
|
* @internal this method will be moved to EXT:workspaces |
|
922
|
|
|
*/ |
|
923
|
|
|
public function workspaceCannotEditOfflineVersion($table, $recData) |
|
924
|
|
|
{ |
|
925
|
|
|
if (!BackendUtility::isTableWorkspaceEnabled($table)) { |
|
926
|
|
|
return 'Table does not support versioning.'; |
|
927
|
|
|
} |
|
928
|
|
|
if (!is_array($recData)) { |
|
929
|
|
|
$recData = BackendUtility::getRecord($table, $recData, 'uid,pid,t3ver_oid,t3ver_wsid,t3ver_state,t3ver_stage'); |
|
930
|
|
|
} |
|
931
|
|
|
if (is_array($recData)) { |
|
932
|
|
|
$versionState = new VersionState($recData['t3ver_state']); |
|
933
|
|
|
if ($versionState->equals(VersionState::NEW_PLACEHOLDER) || (int)$recData['t3ver_oid'] > 0) { |
|
934
|
|
|
return $this->workspaceCannotEditRecord($table, $recData); |
|
935
|
|
|
} |
|
936
|
|
|
return 'Not an offline version'; |
|
937
|
|
|
} |
|
938
|
|
|
return 'No record'; |
|
939
|
|
|
} |
|
940
|
|
|
|
|
941
|
|
|
/** |
|
942
|
|
|
* Checks if a record is allowed to be edited in the current workspace. |
|
943
|
|
|
* This is not bound to an actual record, but to the mere fact if the user is in a workspace |
|
944
|
|
|
* and depending on the table settings. |
|
945
|
|
|
* |
|
946
|
|
|
* @param string $table |
|
947
|
|
|
* @return bool |
|
948
|
|
|
* @internal should only be used from within TYPO3 Core |
|
949
|
|
|
*/ |
|
950
|
|
|
public function workspaceAllowsLiveEditingInTable(string $table): bool |
|
951
|
|
|
{ |
|
952
|
|
|
// In live workspace the record can be added/modified |
|
953
|
|
|
if ($this->workspace === 0) { |
|
954
|
|
|
return true; |
|
955
|
|
|
} |
|
956
|
|
|
// Workspace setting allows to "live edit" records of tables without versioning |
|
957
|
|
|
if (($this->workspaceRec['live_edit'] ?? false) |
|
958
|
|
|
&& !BackendUtility::isTableWorkspaceEnabled($table) |
|
959
|
|
|
) { |
|
960
|
|
|
return true; |
|
961
|
|
|
} |
|
962
|
|
|
// Always for Live workspace AND if live-edit is enabled |
|
963
|
|
|
// and tables are completely without versioning it is ok as well. |
|
964
|
|
|
if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS_alwaysAllowLiveEdit'] ?? false) { |
|
965
|
|
|
return true; |
|
966
|
|
|
} |
|
967
|
|
|
// If the answer is FALSE it means the only valid way to create or edit records by creating records in the workspace |
|
968
|
|
|
return false; |
|
969
|
|
|
} |
|
970
|
|
|
|
|
971
|
|
|
/** |
|
972
|
|
|
* Evaluates if a record from $table can be created. If the table is not set up for versioning, |
|
973
|
|
|
* and the "live edit" flag of the page is set, return false. In live workspace this is always true, |
|
974
|
|
|
* as all records can be created in live workspace |
|
975
|
|
|
* |
|
976
|
|
|
* @param string $table Table name |
|
977
|
|
|
* @return bool |
|
978
|
|
|
* @internal should only be used from within TYPO3 Core |
|
979
|
|
|
*/ |
|
980
|
|
|
public function workspaceCanCreateNewRecord(string $table): bool |
|
981
|
|
|
{ |
|
982
|
|
|
// If LIVE records cannot be created due to workspace restrictions, prepare creation of placeholder-record |
|
983
|
|
|
if (!$this->workspaceAllowsLiveEditingInTable($table) && !BackendUtility::isTableWorkspaceEnabled($table)) { |
|
984
|
|
|
return false; |
|
985
|
|
|
} |
|
986
|
|
|
return true; |
|
987
|
|
|
} |
|
988
|
|
|
|
|
989
|
|
|
/** |
|
990
|
|
|
* Evaluates if auto creation of a version of a record is allowed. |
|
991
|
|
|
* Auto-creation of version: In offline workspace, test if versioning is |
|
992
|
|
|
* enabled and look for workspace version of input record. |
|
993
|
|
|
* If there is no versionized record found we will create one and save to that. |
|
994
|
|
|
* |
|
995
|
|
|
* @param string $table Table of the record |
|
996
|
|
|
* @param int $id UID of record |
|
997
|
|
|
* @param int $recpid PID of record |
|
998
|
|
|
* @return bool TRUE if ok. |
|
999
|
|
|
* @internal should only be used from within TYPO3 Core |
|
1000
|
|
|
*/ |
|
1001
|
|
|
public function workspaceAllowAutoCreation($table, $id, $recpid) |
|
1002
|
|
|
{ |
|
1003
|
|
|
// No version can be created in live workspace |
|
1004
|
|
|
if ($this->workspace === 0) { |
|
1005
|
|
|
return false; |
|
1006
|
|
|
} |
|
1007
|
|
|
// No versioning support for this table, so no version can be created |
|
1008
|
|
|
if (!BackendUtility::isTableWorkspaceEnabled($table)) { |
|
1009
|
|
|
return false; |
|
1010
|
|
|
} |
|
1011
|
|
|
if ($recpid < 0) { |
|
1012
|
|
|
return false; |
|
1013
|
|
|
} |
|
1014
|
|
|
// There must be no existing version of this record in workspace |
|
1015
|
|
|
if (BackendUtility::getWorkspaceVersionOfRecord($this->workspace, $table, $id, 'uid')) { |
|
1016
|
|
|
return false; |
|
1017
|
|
|
} |
|
1018
|
|
|
return true; |
|
1019
|
|
|
} |
|
1020
|
|
|
|
|
1021
|
|
|
/** |
|
1022
|
|
|
* Checks if an element stage allows access for the user in the current workspace |
|
1023
|
|
|
* In live workspace (= 0) access is always granted for any stage. |
|
1024
|
|
|
* Admins are always allowed. |
|
1025
|
|
|
* An option for custom workspaces allows members to also edit when the stage is "Review" |
|
1026
|
|
|
* |
|
1027
|
|
|
* @param int $stage Stage id from an element: -1,0 = editing, 1 = reviewer, >1 = owner |
|
1028
|
|
|
* @return bool TRUE if user is allowed access |
|
1029
|
|
|
* @internal should only be used from within TYPO3 Core |
|
1030
|
|
|
*/ |
|
1031
|
|
|
public function workspaceCheckStageForCurrent($stage) |
|
1032
|
|
|
{ |
|
1033
|
|
|
// Always allow for admins |
|
1034
|
|
|
if ($this->isAdmin()) { |
|
1035
|
|
|
return true; |
|
1036
|
|
|
} |
|
1037
|
|
|
// Always OK for live workspace |
|
1038
|
|
|
if ($this->workspace === 0 || !ExtensionManagementUtility::isLoaded('workspaces')) { |
|
1039
|
|
|
return true; |
|
1040
|
|
|
} |
|
1041
|
|
|
$stage = (int)$stage; |
|
1042
|
|
|
$stat = $this->checkWorkspaceCurrent(); |
|
1043
|
|
|
$accessType = $stat['_ACCESS']; |
|
1044
|
|
|
// Workspace owners are always allowed for stage change |
|
1045
|
|
|
if ($accessType === 'owner') { |
|
1046
|
|
|
return true; |
|
1047
|
|
|
} |
|
1048
|
|
|
|
|
1049
|
|
|
// Check if custom staging is activated |
|
1050
|
|
|
$workspaceRec = BackendUtility::getRecord('sys_workspace', $stat['uid']); |
|
1051
|
|
|
if ($workspaceRec['custom_stages'] > 0 && $stage !== 0 && $stage !== -10) { |
|
1052
|
|
|
// Get custom stage record |
|
1053
|
|
|
$workspaceStageRec = BackendUtility::getRecord('sys_workspace_stage', $stage); |
|
1054
|
|
|
// Check if the user is responsible for the current stage |
|
1055
|
|
|
if ( |
|
1056
|
|
|
$accessType === 'member' |
|
1057
|
|
|
&& GeneralUtility::inList($workspaceStageRec['responsible_persons'], 'be_users_' . $this->user['uid']) |
|
1058
|
|
|
) { |
|
1059
|
|
|
return true; |
|
1060
|
|
|
} |
|
1061
|
|
|
// Check if the user is in a group which is responsible for the current stage |
|
1062
|
|
|
foreach ($this->userGroupsUID as $groupUid) { |
|
1063
|
|
|
if ( |
|
1064
|
|
|
$accessType === 'member' |
|
1065
|
|
|
&& GeneralUtility::inList($workspaceStageRec['responsible_persons'], 'be_groups_' . $groupUid) |
|
1066
|
|
|
) { |
|
1067
|
|
|
return true; |
|
1068
|
|
|
} |
|
1069
|
|
|
} |
|
1070
|
|
|
} elseif ($stage === -10 || $stage === -20) { |
|
1071
|
|
|
// Nobody is allowed to do that except the owner (which was checked above) |
|
1072
|
|
|
return false; |
|
1073
|
|
|
} elseif ( |
|
1074
|
|
|
$accessType === 'reviewer' && $stage <= 1 |
|
|
|
|
|
|
1075
|
|
|
|| $accessType === 'member' && $stage <= 0 |
|
1076
|
|
|
) { |
|
1077
|
|
|
return true; |
|
1078
|
|
|
} |
|
1079
|
|
|
return false; |
|
1080
|
|
|
} |
|
1081
|
|
|
|
|
1082
|
|
|
/** |
|
1083
|
|
|
* Returns TRUE if the user has access to publish content from the workspace ID given. |
|
1084
|
|
|
* Admin-users are always granted access to do this |
|
1085
|
|
|
* If the workspace ID is 0 (live) all users have access also |
|
1086
|
|
|
* For custom workspaces it depends on whether the user is owner OR like with |
|
1087
|
|
|
* draft workspace if the user has access to Live workspace. |
|
1088
|
|
|
* |
|
1089
|
|
|
* @param int $wsid Workspace UID; 0,1+ |
|
1090
|
|
|
* @return bool Returns TRUE if the user has access to publish content from the workspace ID given. |
|
1091
|
|
|
* @internal this method will be moved to EXT:workspaces |
|
1092
|
|
|
*/ |
|
1093
|
|
|
public function workspacePublishAccess($wsid) |
|
1094
|
|
|
{ |
|
1095
|
|
|
if ($this->isAdmin()) { |
|
1096
|
|
|
return true; |
|
1097
|
|
|
} |
|
1098
|
|
|
$wsAccess = $this->checkWorkspace($wsid); |
|
1099
|
|
|
// If no access to workspace, of course you cannot publish! |
|
1100
|
|
|
if ($wsAccess === false) { |
|
|
|
|
|
|
1101
|
|
|
return false; |
|
1102
|
|
|
} |
|
1103
|
|
|
if ((int)$wsAccess['uid'] === 0) { |
|
1104
|
|
|
// If access to Live workspace, no problem. |
|
1105
|
|
|
return true; |
|
1106
|
|
|
} |
|
1107
|
|
|
// Custom workspaces |
|
1108
|
|
|
// 1. Owners can always publish |
|
1109
|
|
|
if ($wsAccess['_ACCESS'] === 'owner') { |
|
1110
|
|
|
return true; |
|
1111
|
|
|
} |
|
1112
|
|
|
// 2. User has access to online workspace which is OK as well as long as publishing |
|
1113
|
|
|
// access is not limited by workspace option. |
|
1114
|
|
|
return $this->checkWorkspace(0) && !($wsAccess['publish_access'] & 2); |
|
1115
|
|
|
} |
|
1116
|
|
|
|
|
1117
|
|
|
/** |
|
1118
|
|
|
* Returns full parsed user TSconfig array, merged with TSconfig from groups. |
|
1119
|
|
|
* |
|
1120
|
|
|
* Example: |
|
1121
|
|
|
* [ |
|
1122
|
|
|
* 'options.' => [ |
|
1123
|
|
|
* 'fooEnabled' => '0', |
|
1124
|
|
|
* 'fooEnabled.' => [ |
|
1125
|
|
|
* 'tt_content' => 1, |
|
1126
|
|
|
* ], |
|
1127
|
|
|
* ], |
|
1128
|
|
|
* ] |
|
1129
|
|
|
* |
|
1130
|
|
|
* @return array Parsed and merged user TSconfig array |
|
1131
|
|
|
*/ |
|
1132
|
|
|
public function getTSConfig() |
|
1133
|
|
|
{ |
|
1134
|
|
|
return $this->userTS; |
|
1135
|
|
|
} |
|
1136
|
|
|
|
|
1137
|
|
|
/** |
|
1138
|
|
|
* Returns an array with the webmounts. |
|
1139
|
|
|
* If no webmounts, and empty array is returned. |
|
1140
|
|
|
* Webmounts permissions are checked in fetchGroupData() |
|
1141
|
|
|
* |
|
1142
|
|
|
* @return array of web mounts uids (may include '0') |
|
1143
|
|
|
*/ |
|
1144
|
|
|
public function returnWebmounts() |
|
1145
|
|
|
{ |
|
1146
|
|
|
return (string)$this->groupData['webmounts'] != '' ? explode(',', $this->groupData['webmounts']) : []; |
|
1147
|
|
|
} |
|
1148
|
|
|
|
|
1149
|
|
|
/** |
|
1150
|
|
|
* Initializes the given mount points for the current Backend user. |
|
1151
|
|
|
* |
|
1152
|
|
|
* @param array $mountPointUids Page UIDs that should be used as web mountpoints |
|
1153
|
|
|
* @param bool $append If TRUE the given mount point will be appended. Otherwise the current mount points will be replaced. |
|
1154
|
|
|
*/ |
|
1155
|
|
|
public function setWebmounts(array $mountPointUids, $append = false) |
|
1156
|
|
|
{ |
|
1157
|
|
|
if (empty($mountPointUids)) { |
|
1158
|
|
|
return; |
|
1159
|
|
|
} |
|
1160
|
|
|
if ($append) { |
|
1161
|
|
|
$currentWebMounts = GeneralUtility::intExplode(',', $this->groupData['webmounts']); |
|
1162
|
|
|
$mountPointUids = array_merge($currentWebMounts, $mountPointUids); |
|
1163
|
|
|
} |
|
1164
|
|
|
$this->groupData['webmounts'] = implode(',', array_unique($mountPointUids)); |
|
1165
|
|
|
} |
|
1166
|
|
|
|
|
1167
|
|
|
/** |
|
1168
|
|
|
* Checks for alternative web mount points for the element browser. |
|
1169
|
|
|
* |
|
1170
|
|
|
* If there is a temporary mount point active in the page tree it will be used. |
|
1171
|
|
|
* |
|
1172
|
|
|
* If the User TSconfig options.pageTree.altElementBrowserMountPoints is not empty the pages configured |
|
1173
|
|
|
* there are used as web mounts If options.pageTree.altElementBrowserMountPoints.append is enabled, |
|
1174
|
|
|
* they are appended to the existing webmounts. |
|
1175
|
|
|
* |
|
1176
|
|
|
* @internal - do not use in your own extension |
|
1177
|
|
|
*/ |
|
1178
|
|
|
public function initializeWebmountsForElementBrowser() |
|
1179
|
|
|
{ |
|
1180
|
|
|
$alternativeWebmountPoint = (int)$this->getSessionData('pageTree_temporaryMountPoint'); |
|
1181
|
|
|
if ($alternativeWebmountPoint) { |
|
1182
|
|
|
$alternativeWebmountPoint = GeneralUtility::intExplode(',', (string)$alternativeWebmountPoint); |
|
1183
|
|
|
$this->setWebmounts($alternativeWebmountPoint); |
|
1184
|
|
|
return; |
|
1185
|
|
|
} |
|
1186
|
|
|
|
|
1187
|
|
|
$alternativeWebmountPoints = trim($this->getTSConfig()['options.']['pageTree.']['altElementBrowserMountPoints'] ?? ''); |
|
1188
|
|
|
$appendAlternativeWebmountPoints = $this->getTSConfig()['options.']['pageTree.']['altElementBrowserMountPoints.']['append'] ?? ''; |
|
1189
|
|
|
if ($alternativeWebmountPoints) { |
|
1190
|
|
|
$alternativeWebmountPoints = GeneralUtility::intExplode(',', $alternativeWebmountPoints); |
|
1191
|
|
|
$this->setWebmounts($alternativeWebmountPoints, $appendAlternativeWebmountPoints); |
|
|
|
|
|
|
1192
|
|
|
} |
|
1193
|
|
|
} |
|
1194
|
|
|
|
|
1195
|
|
|
/** |
|
1196
|
|
|
* Returns TRUE or FALSE, depending if an alert popup (a javascript confirmation) should be shown |
|
1197
|
|
|
* call like $GLOBALS['BE_USER']->jsConfirmation($BITMASK). |
|
1198
|
|
|
* |
|
1199
|
|
|
* @param int $bitmask Bitmask, one of \TYPO3\CMS\Core\Type\Bitmask\JsConfirmation |
|
1200
|
|
|
* @return bool TRUE if the confirmation should be shown |
|
1201
|
|
|
* @see JsConfirmation |
|
1202
|
|
|
*/ |
|
1203
|
|
|
public function jsConfirmation($bitmask) |
|
1204
|
|
|
{ |
|
1205
|
|
|
try { |
|
1206
|
|
|
$alertPopupsSetting = trim((string)($this->getTSConfig()['options.']['alertPopups'] ?? '')); |
|
1207
|
|
|
$alertPopup = JsConfirmation::cast($alertPopupsSetting === '' ? null : (int)$alertPopupsSetting); |
|
1208
|
|
|
} catch (InvalidEnumerationValueException $e) { |
|
1209
|
|
|
$alertPopup = new JsConfirmation(); |
|
1210
|
|
|
} |
|
1211
|
|
|
|
|
1212
|
|
|
return JsConfirmation::cast($bitmask)->matches($alertPopup); |
|
1213
|
|
|
} |
|
1214
|
|
|
|
|
1215
|
|
|
/** |
|
1216
|
|
|
* Initializes a lot of stuff like the access-lists, database-mountpoints and filemountpoints |
|
1217
|
|
|
* This method is called by ->backendCheckLogin() (from extending BackendUserAuthentication) |
|
1218
|
|
|
* if the backend user login has verified OK. |
|
1219
|
|
|
* Generally this is required initialization of a backend user. |
|
1220
|
|
|
* |
|
1221
|
|
|
* @internal |
|
1222
|
|
|
* @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser |
|
1223
|
|
|
*/ |
|
1224
|
|
|
public function fetchGroupData() |
|
1225
|
|
|
{ |
|
1226
|
|
|
if ($this->user['uid']) { |
|
1227
|
|
|
// Get lists for the be_user record and set them as default/primary values. |
|
1228
|
|
|
// Enabled Backend Modules |
|
1229
|
|
|
$this->groupData['modules'] = $this->user['userMods']; |
|
1230
|
|
|
// Add available widgets |
|
1231
|
|
|
$this->groupData['available_widgets'] = $this->user['available_widgets'] ?? ''; |
|
1232
|
|
|
// Add allowed mfa providers |
|
1233
|
|
|
$this->groupData['mfa_providers'] = $this->user['mfa_providers'] ?? ''; |
|
1234
|
|
|
// Add Allowed Languages |
|
1235
|
|
|
$this->groupData['allowed_languages'] = $this->user['allowed_languages'] ?? ''; |
|
1236
|
|
|
// Set user value for workspace permissions. |
|
1237
|
|
|
$this->groupData['workspace_perms'] = $this->user['workspace_perms']; |
|
1238
|
|
|
// Database mountpoints |
|
1239
|
|
|
$this->groupData['webmounts'] = $this->user['db_mountpoints']; |
|
1240
|
|
|
// File mountpoints |
|
1241
|
|
|
$this->groupData['filemounts'] = $this->user['file_mountpoints']; |
|
1242
|
|
|
// Fileoperation permissions |
|
1243
|
|
|
$this->groupData['file_permissions'] = $this->user['file_permissions']; |
|
1244
|
|
|
|
|
1245
|
|
|
// Get the groups and accumulate their permission settings |
|
1246
|
|
|
$mountOptions = new BackendGroupMountOption($this->user['options']); |
|
1247
|
|
|
$groupResolver = GeneralUtility::makeInstance(GroupResolver::class); |
|
1248
|
|
|
$resolvedGroups = $groupResolver->resolveGroupsForUser($this->user, $this->usergroup_table); |
|
1249
|
|
|
foreach ($resolvedGroups as $groupInfo) { |
|
1250
|
|
|
$groupInfo += [ |
|
1251
|
|
|
'uid' => 0, |
|
1252
|
|
|
'db_mountpoints' => '', |
|
1253
|
|
|
'file_mountpoints' => '', |
|
1254
|
|
|
'groupMods' => '', |
|
1255
|
|
|
'availableWidgets' => '', |
|
1256
|
|
|
'mfa_providers' => '', |
|
1257
|
|
|
'tables_select' => '', |
|
1258
|
|
|
'tables_modify' => '', |
|
1259
|
|
|
'pagetypes_select' => '', |
|
1260
|
|
|
'non_exclude_fields' => '', |
|
1261
|
|
|
'explicit_allowdeny' => '', |
|
1262
|
|
|
'allowed_languages' => '', |
|
1263
|
|
|
'custom_options' => '', |
|
1264
|
|
|
'file_permissions' => '', |
|
1265
|
|
|
'workspace_perms' => 0, // Bitflag. |
|
1266
|
|
|
]; |
|
1267
|
|
|
// Add the group uid to internal arrays. |
|
1268
|
|
|
$this->userGroupsUID[] = (int)$groupInfo['uid']; |
|
1269
|
|
|
$this->userGroups[(int)$groupInfo['uid']] = $groupInfo; |
|
1270
|
|
|
// Mount group database-mounts |
|
1271
|
|
|
if ($mountOptions->shouldUserIncludePageMountsFromAssociatedGroups()) { |
|
1272
|
|
|
$this->groupData['webmounts'] .= ',' . $groupInfo['db_mountpoints']; |
|
1273
|
|
|
} |
|
1274
|
|
|
// Mount group file-mounts |
|
1275
|
|
|
if ($mountOptions->shouldUserIncludePageMountsFromAssociatedGroups()) { |
|
1276
|
|
|
$this->groupData['filemounts'] .= ',' . $groupInfo['file_mountpoints']; |
|
1277
|
|
|
} |
|
1278
|
|
|
// Gather permission detail fields |
|
1279
|
|
|
$this->groupData['modules'] .= ',' . $groupInfo['groupMods']; |
|
1280
|
|
|
$this->groupData['available_widgets'] .= ',' . $groupInfo['availableWidgets']; |
|
1281
|
|
|
$this->groupData['mfa_providers'] .= ',' . $groupInfo['mfa_providers']; |
|
1282
|
|
|
$this->groupData['tables_select'] .= ',' . $groupInfo['tables_select']; |
|
1283
|
|
|
$this->groupData['tables_modify'] .= ',' . $groupInfo['tables_modify']; |
|
1284
|
|
|
$this->groupData['pagetypes_select'] .= ',' . $groupInfo['pagetypes_select']; |
|
1285
|
|
|
$this->groupData['non_exclude_fields'] .= ',' . $groupInfo['non_exclude_fields']; |
|
1286
|
|
|
$this->groupData['explicit_allowdeny'] .= ',' . $groupInfo['explicit_allowdeny']; |
|
1287
|
|
|
$this->groupData['allowed_languages'] .= ',' . $groupInfo['allowed_languages']; |
|
1288
|
|
|
$this->groupData['custom_options'] .= ',' . $groupInfo['custom_options']; |
|
1289
|
|
|
$this->groupData['file_permissions'] .= ',' . $groupInfo['file_permissions']; |
|
1290
|
|
|
// Setting workspace permissions: |
|
1291
|
|
|
$this->groupData['workspace_perms'] |= $groupInfo['workspace_perms']; |
|
1292
|
|
|
if (!$this->firstMainGroup) { |
|
1293
|
|
|
$this->firstMainGroup = (int)$groupInfo['uid']; |
|
1294
|
|
|
} |
|
1295
|
|
|
} |
|
1296
|
|
|
|
|
1297
|
|
|
// Populating the $this->userGroupsUID -array with the groups in the order in which they were LAST included.!! |
|
1298
|
|
|
// Finally this is the list of group_uid's in the order they are parsed (including subgroups!) |
|
1299
|
|
|
// and without duplicates (duplicates are presented with their last entrance in the list, |
|
1300
|
|
|
// which thus reflects the order of the TypoScript in TSconfig) |
|
1301
|
|
|
$this->userGroupsUID = array_reverse(array_unique(array_reverse($this->userGroupsUID))); |
|
1302
|
|
|
|
|
1303
|
|
|
$this->prepareUserTsConfig(); |
|
1304
|
|
|
|
|
1305
|
|
|
// Processing webmounts |
|
1306
|
|
|
// Admin's always have the root mounted |
|
1307
|
|
|
if ($this->isAdmin() && !($this->getTSConfig()['options.']['dontMountAdminMounts'] ?? false)) { |
|
1308
|
|
|
$this->groupData['webmounts'] = '0,' . $this->groupData['webmounts']; |
|
1309
|
|
|
} |
|
1310
|
|
|
// The lists are cleaned for duplicates |
|
1311
|
|
|
$this->groupData['webmounts'] = StringUtility::uniqueList($this->groupData['webmounts'] ?? ''); |
|
1312
|
|
|
$this->groupData['pagetypes_select'] = StringUtility::uniqueList($this->groupData['pagetypes_select'] ?? ''); |
|
1313
|
|
|
$this->groupData['tables_select'] = StringUtility::uniqueList(($this->groupData['tables_modify'] ?? '') . ',' . ($this->groupData['tables_select'] ?? '')); |
|
1314
|
|
|
$this->groupData['tables_modify'] = StringUtility::uniqueList($this->groupData['tables_modify'] ?? ''); |
|
1315
|
|
|
$this->groupData['non_exclude_fields'] = StringUtility::uniqueList($this->groupData['non_exclude_fields'] ?? ''); |
|
1316
|
|
|
$this->groupData['explicit_allowdeny'] = StringUtility::uniqueList($this->groupData['explicit_allowdeny'] ?? ''); |
|
1317
|
|
|
$this->groupData['allowed_languages'] = StringUtility::uniqueList($this->groupData['allowed_languages'] ?? ''); |
|
1318
|
|
|
$this->groupData['custom_options'] = StringUtility::uniqueList($this->groupData['custom_options'] ?? ''); |
|
1319
|
|
|
$this->groupData['modules'] = StringUtility::uniqueList($this->groupData['modules'] ?? ''); |
|
1320
|
|
|
$this->groupData['available_widgets'] = StringUtility::uniqueList($this->groupData['available_widgets'] ?? ''); |
|
1321
|
|
|
$this->groupData['mfa_providers'] = StringUtility::uniqueList($this->groupData['mfa_providers'] ?? ''); |
|
1322
|
|
|
$this->groupData['file_permissions'] = StringUtility::uniqueList($this->groupData['file_permissions'] ?? ''); |
|
1323
|
|
|
|
|
1324
|
|
|
// Check if the user access to all web mounts set |
|
1325
|
|
|
if (!empty(trim($this->groupData['webmounts']))) { |
|
1326
|
|
|
$validWebMounts = $this->filterValidWebMounts($this->groupData['webmounts']); |
|
1327
|
|
|
$this->groupData['webmounts'] = implode(',', $validWebMounts); |
|
1328
|
|
|
} |
|
1329
|
|
|
// Setting up workspace situation (after webmounts are processed!): |
|
1330
|
|
|
$this->workspaceInit(); |
|
1331
|
|
|
} |
|
1332
|
|
|
} |
|
1333
|
|
|
|
|
1334
|
|
|
/** |
|
1335
|
|
|
* Checking read access to web mounts, but keeps "0" or empty strings. |
|
1336
|
|
|
* In any case, checks if the list of pages is visible for the backend user but also |
|
1337
|
|
|
* if the page is not deleted. |
|
1338
|
|
|
* |
|
1339
|
|
|
* @param string $listOfWebMounts a comma-separated list of webmounts, could also be empty, or contain "0" |
|
1340
|
|
|
* @return array a list of all valid web mounts the user has access to |
|
1341
|
|
|
*/ |
|
1342
|
|
|
protected function filterValidWebMounts(string $listOfWebMounts): array |
|
1343
|
|
|
{ |
|
1344
|
|
|
// Checking read access to web mounts if there are mounts points (not empty string, false or 0) |
|
1345
|
|
|
$allWebMounts = explode(',', $listOfWebMounts); |
|
1346
|
|
|
// Selecting all web mounts with permission clause for reading |
|
1347
|
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); |
|
1348
|
|
|
$queryBuilder->getRestrictions() |
|
1349
|
|
|
->removeAll() |
|
1350
|
|
|
->add(GeneralUtility::makeInstance(DeletedRestriction::class)); |
|
1351
|
|
|
|
|
1352
|
|
|
$readablePagesOfWebMounts = $queryBuilder->select('uid') |
|
1353
|
|
|
->from('pages') |
|
1354
|
|
|
// @todo DOCTRINE: check how to make getPagePermsClause() portable |
|
1355
|
|
|
->where( |
|
1356
|
|
|
$this->getPagePermsClause(Permission::PAGE_SHOW), |
|
1357
|
|
|
$queryBuilder->expr()->in( |
|
1358
|
|
|
'uid', |
|
1359
|
|
|
$queryBuilder->createNamedParameter( |
|
1360
|
|
|
GeneralUtility::intExplode(',', $listOfWebMounts), |
|
1361
|
|
|
Connection::PARAM_INT_ARRAY |
|
1362
|
|
|
) |
|
1363
|
|
|
) |
|
1364
|
|
|
) |
|
1365
|
|
|
->execute() |
|
1366
|
|
|
->fetchAll(); |
|
1367
|
|
|
$readablePagesOfWebMounts = array_column(($readablePagesOfWebMounts ?: []), 'uid', 'uid'); |
|
1368
|
|
|
foreach ($allWebMounts as $key => $mountPointUid) { |
|
1369
|
|
|
// If the mount ID is NOT found among selected pages, unset it: |
|
1370
|
|
|
if ($mountPointUid > 0 && !isset($readablePagesOfWebMounts[$mountPointUid])) { |
|
1371
|
|
|
unset($allWebMounts[$key]); |
|
1372
|
|
|
} |
|
1373
|
|
|
} |
|
1374
|
|
|
return $allWebMounts; |
|
1375
|
|
|
} |
|
1376
|
|
|
|
|
1377
|
|
|
/** |
|
1378
|
|
|
* This method parses the UserTSconfig from the current user and all their groups. |
|
1379
|
|
|
* If the contents are the same, parsing is skipped. No matching is applied here currently. |
|
1380
|
|
|
*/ |
|
1381
|
|
|
protected function prepareUserTsConfig(): void |
|
1382
|
|
|
{ |
|
1383
|
|
|
$collectedUserTSconfig = [ |
|
1384
|
|
|
'default' => $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] |
|
1385
|
|
|
]; |
|
1386
|
|
|
// Default TSconfig for admin-users |
|
1387
|
|
|
if ($this->isAdmin()) { |
|
1388
|
|
|
$collectedUserTSconfig[] = 'admPanel.enable.all = 1'; |
|
1389
|
|
|
} |
|
1390
|
|
|
// Setting defaults for sys_note author / email |
|
1391
|
|
|
$collectedUserTSconfig[] = ' |
|
1392
|
|
|
TCAdefaults.sys_note.author = ' . $this->user['realName'] . ' |
|
1393
|
|
|
TCAdefaults.sys_note.email = ' . $this->user['email']; |
|
1394
|
|
|
|
|
1395
|
|
|
// Loop through all groups and add their 'TSconfig' fields |
|
1396
|
|
|
foreach ($this->userGroupsUID as $groupId) { |
|
1397
|
|
|
$collectedUserTSconfig['group_' . $groupId] = $this->userGroups[$groupId]['TSconfig'] ?? ''; |
|
1398
|
|
|
} |
|
1399
|
|
|
|
|
1400
|
|
|
$collectedUserTSconfig[] = $this->user['TSconfig']; |
|
1401
|
|
|
// Check external files |
|
1402
|
|
|
$collectedUserTSconfig = TypoScriptParser::checkIncludeLines_array($collectedUserTSconfig); |
|
1403
|
|
|
// Imploding with "[global]" will make sure that non-ended confinements with braces are ignored. |
|
1404
|
|
|
$userTS_text = implode("\n[GLOBAL]\n", $collectedUserTSconfig); |
|
1405
|
|
|
// Parsing the user TSconfig (or getting from cache) |
|
1406
|
|
|
$hash = md5('userTS:' . $userTS_text); |
|
1407
|
|
|
$cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash'); |
|
1408
|
|
|
if (!($this->userTS = $cache->get($hash))) { |
|
1409
|
|
|
$parseObj = GeneralUtility::makeInstance(TypoScriptParser::class); |
|
1410
|
|
|
$conditionMatcher = GeneralUtility::makeInstance(ConditionMatcher::class); |
|
1411
|
|
|
$parseObj->parse($userTS_text, $conditionMatcher); |
|
1412
|
|
|
$this->userTS = $parseObj->setup; |
|
1413
|
|
|
$cache->set($hash, $this->userTS, ['UserTSconfig'], 0); |
|
1414
|
|
|
// Ensure to update UC later |
|
1415
|
|
|
$this->userTSUpdated = true; |
|
1416
|
|
|
} |
|
1417
|
|
|
} |
|
1418
|
|
|
|
|
1419
|
|
|
/** |
|
1420
|
|
|
* Sets up all file storages for a user. |
|
1421
|
|
|
* Needs to be called AFTER the groups have been loaded. |
|
1422
|
|
|
*/ |
|
1423
|
|
|
protected function initializeFileStorages() |
|
1424
|
|
|
{ |
|
1425
|
|
|
$this->fileStorages = []; |
|
1426
|
|
|
/** @var \TYPO3\CMS\Core\Resource\StorageRepository $storageRepository */ |
|
1427
|
|
|
$storageRepository = GeneralUtility::makeInstance(StorageRepository::class); |
|
1428
|
|
|
// Admin users have all file storages visible, without any filters |
|
1429
|
|
|
if ($this->isAdmin()) { |
|
1430
|
|
|
$storageObjects = $storageRepository->findAll(); |
|
1431
|
|
|
foreach ($storageObjects as $storageObject) { |
|
1432
|
|
|
$this->fileStorages[$storageObject->getUid()] = $storageObject; |
|
1433
|
|
|
} |
|
1434
|
|
|
} else { |
|
1435
|
|
|
// Regular users only have storages that are defined in their filemounts |
|
1436
|
|
|
// Permissions and file mounts for the storage are added in StoragePermissionAspect |
|
1437
|
|
|
foreach ($this->getFileMountRecords() as $row) { |
|
1438
|
|
|
if (!array_key_exists((int)$row['base'], $this->fileStorages)) { |
|
1439
|
|
|
$storageObject = $storageRepository->findByUid($row['base']); |
|
1440
|
|
|
if ($storageObject) { |
|
1441
|
|
|
$this->fileStorages[$storageObject->getUid()] = $storageObject; |
|
1442
|
|
|
} |
|
1443
|
|
|
} |
|
1444
|
|
|
} |
|
1445
|
|
|
} |
|
1446
|
|
|
|
|
1447
|
|
|
// This has to be called always in order to set certain filters |
|
1448
|
|
|
$this->evaluateUserSpecificFileFilterSettings(); |
|
1449
|
|
|
} |
|
1450
|
|
|
|
|
1451
|
|
|
/** |
|
1452
|
|
|
* Returns an array of category mount points. The category permissions from BE Groups |
|
1453
|
|
|
* are also taken into consideration and are merged into User permissions. |
|
1454
|
|
|
* |
|
1455
|
|
|
* @return array |
|
1456
|
|
|
*/ |
|
1457
|
|
|
public function getCategoryMountPoints() |
|
1458
|
|
|
{ |
|
1459
|
|
|
$categoryMountPoints = ''; |
|
1460
|
|
|
|
|
1461
|
|
|
// Category mounts of the groups |
|
1462
|
|
|
if (is_array($this->userGroups)) { |
|
|
|
|
|
|
1463
|
|
|
foreach ($this->userGroups as $group) { |
|
1464
|
|
|
if ($group['category_perms']) { |
|
1465
|
|
|
$categoryMountPoints .= ',' . $group['category_perms']; |
|
1466
|
|
|
} |
|
1467
|
|
|
} |
|
1468
|
|
|
} |
|
1469
|
|
|
|
|
1470
|
|
|
// Category mounts of the user record |
|
1471
|
|
|
if ($this->user['category_perms']) { |
|
1472
|
|
|
$categoryMountPoints .= ',' . $this->user['category_perms']; |
|
1473
|
|
|
} |
|
1474
|
|
|
|
|
1475
|
|
|
// Make the ids unique |
|
1476
|
|
|
$categoryMountPoints = GeneralUtility::trimExplode(',', $categoryMountPoints); |
|
1477
|
|
|
$categoryMountPoints = array_filter($categoryMountPoints); // remove empty value |
|
1478
|
|
|
$categoryMountPoints = array_unique($categoryMountPoints); // remove unique value |
|
1479
|
|
|
|
|
1480
|
|
|
return $categoryMountPoints; |
|
1481
|
|
|
} |
|
1482
|
|
|
|
|
1483
|
|
|
/** |
|
1484
|
|
|
* Returns an array of file mount records, taking workspaces and user home and group home directories into account |
|
1485
|
|
|
* Needs to be called AFTER the groups have been loaded. |
|
1486
|
|
|
* |
|
1487
|
|
|
* @return array |
|
1488
|
|
|
* @internal |
|
1489
|
|
|
*/ |
|
1490
|
|
|
public function getFileMountRecords() |
|
1491
|
|
|
{ |
|
1492
|
|
|
$runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'); |
|
1493
|
|
|
$fileMountRecordCache = $runtimeCache->get('backendUserAuthenticationFileMountRecords') ?: []; |
|
1494
|
|
|
|
|
1495
|
|
|
if (!empty($fileMountRecordCache)) { |
|
1496
|
|
|
return $fileMountRecordCache; |
|
1497
|
|
|
} |
|
1498
|
|
|
|
|
1499
|
|
|
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); |
|
1500
|
|
|
|
|
1501
|
|
|
// Processing file mounts (both from the user and the groups) |
|
1502
|
|
|
$fileMounts = array_unique(GeneralUtility::intExplode(',', $this->groupData['filemounts'], true)); |
|
1503
|
|
|
|
|
1504
|
|
|
// Limit file mounts if set in workspace record |
|
1505
|
|
|
if ($this->workspace > 0 && !empty($this->workspaceRec['file_mountpoints'])) { |
|
1506
|
|
|
$workspaceFileMounts = GeneralUtility::intExplode(',', $this->workspaceRec['file_mountpoints'], true); |
|
1507
|
|
|
$fileMounts = array_intersect($fileMounts, $workspaceFileMounts); |
|
1508
|
|
|
} |
|
1509
|
|
|
|
|
1510
|
|
|
if (!empty($fileMounts)) { |
|
1511
|
|
|
$orderBy = $GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'] ?? 'sorting'; |
|
1512
|
|
|
|
|
1513
|
|
|
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_filemounts'); |
|
1514
|
|
|
$queryBuilder->getRestrictions() |
|
1515
|
|
|
->removeAll() |
|
1516
|
|
|
->add(GeneralUtility::makeInstance(DeletedRestriction::class)) |
|
1517
|
|
|
->add(GeneralUtility::makeInstance(HiddenRestriction::class)) |
|
1518
|
|
|
->add(GeneralUtility::makeInstance(RootLevelRestriction::class)); |
|
1519
|
|
|
|
|
1520
|
|
|
$queryBuilder->select('*') |
|
1521
|
|
|
->from('sys_filemounts') |
|
1522
|
|
|
->where( |
|
1523
|
|
|
$queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($fileMounts, Connection::PARAM_INT_ARRAY)) |
|
1524
|
|
|
); |
|
1525
|
|
|
|
|
1526
|
|
|
foreach (QueryHelper::parseOrderBy($orderBy) as $fieldAndDirection) { |
|
1527
|
|
|
$queryBuilder->addOrderBy(...$fieldAndDirection); |
|
1528
|
|
|
} |
|
1529
|
|
|
|
|
1530
|
|
|
$fileMountRecords = $queryBuilder->execute()->fetchAll(\PDO::FETCH_ASSOC); |
|
1531
|
|
|
if ($fileMountRecords !== false) { |
|
1532
|
|
|
foreach ($fileMountRecords as $fileMount) { |
|
1533
|
|
|
$fileMountRecordCache[$fileMount['base'] . $fileMount['path']] = $fileMount; |
|
1534
|
|
|
} |
|
1535
|
|
|
} |
|
1536
|
|
|
} |
|
1537
|
|
|
|
|
1538
|
|
|
// Read-only file mounts |
|
1539
|
|
|
$readOnlyMountPoints = \trim($this->getTSConfig()['options.']['folderTree.']['altElementBrowserMountPoints'] ?? ''); |
|
1540
|
|
|
if ($readOnlyMountPoints) { |
|
1541
|
|
|
// We cannot use the API here but need to fetch the default storage record directly |
|
1542
|
|
|
// to not instantiate it (which directly applies mount points) before all mount points are resolved! |
|
1543
|
|
|
$queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_storage'); |
|
1544
|
|
|
$defaultStorageRow = $queryBuilder->select('uid') |
|
1545
|
|
|
->from('sys_file_storage') |
|
1546
|
|
|
->where( |
|
1547
|
|
|
$queryBuilder->expr()->eq('is_default', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)) |
|
1548
|
|
|
) |
|
1549
|
|
|
->setMaxResults(1) |
|
1550
|
|
|
->execute() |
|
1551
|
|
|
->fetch(\PDO::FETCH_ASSOC); |
|
1552
|
|
|
|
|
1553
|
|
|
$readOnlyMountPointArray = GeneralUtility::trimExplode(',', $readOnlyMountPoints); |
|
1554
|
|
|
foreach ($readOnlyMountPointArray as $readOnlyMountPoint) { |
|
1555
|
|
|
$readOnlyMountPointConfiguration = GeneralUtility::trimExplode(':', $readOnlyMountPoint); |
|
1556
|
|
|
if (count($readOnlyMountPointConfiguration) === 2) { |
|
1557
|
|
|
// A storage is passed in the configuration |
|
1558
|
|
|
$storageUid = (int)$readOnlyMountPointConfiguration[0]; |
|
1559
|
|
|
$path = $readOnlyMountPointConfiguration[1]; |
|
1560
|
|
|
} else { |
|
1561
|
|
|
if (empty($defaultStorageRow)) { |
|
1562
|
|
|
throw new \RuntimeException('Read only mount points have been defined in User TsConfig without specific storage, but a default storage could not be resolved.', 1404472382); |
|
1563
|
|
|
} |
|
1564
|
|
|
// Backwards compatibility: If no storage is passed, we use the default storage |
|
1565
|
|
|
$storageUid = $defaultStorageRow['uid']; |
|
1566
|
|
|
$path = $readOnlyMountPointConfiguration[0]; |
|
1567
|
|
|
} |
|
1568
|
|
|
$fileMountRecordCache[$storageUid . $path] = [ |
|
1569
|
|
|
'base' => $storageUid, |
|
1570
|
|
|
'title' => $path, |
|
1571
|
|
|
'path' => $path, |
|
1572
|
|
|
'read_only' => true |
|
1573
|
|
|
]; |
|
1574
|
|
|
} |
|
1575
|
|
|
} |
|
1576
|
|
|
|
|
1577
|
|
|
// Personal or Group filemounts are not accessible if file mount list is set in workspace record |
|
1578
|
|
|
if ($this->workspace <= 0 || empty($this->workspaceRec['file_mountpoints'])) { |
|
1579
|
|
|
// If userHomePath is set, we attempt to mount it |
|
1580
|
|
|
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath']) { |
|
1581
|
|
|
[$userHomeStorageUid, $userHomeFilter] = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['userHomePath'], 2); |
|
1582
|
|
|
$userHomeStorageUid = (int)$userHomeStorageUid; |
|
1583
|
|
|
$userHomeFilter = '/' . ltrim($userHomeFilter, '/'); |
|
1584
|
|
|
if ($userHomeStorageUid > 0) { |
|
1585
|
|
|
// Try and mount with [uid]_[username] |
|
1586
|
|
|
$path = $userHomeFilter . $this->user['uid'] . '_' . $this->user['username'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir']; |
|
1587
|
|
|
$fileMountRecordCache[$userHomeStorageUid . $path] = [ |
|
1588
|
|
|
'base' => $userHomeStorageUid, |
|
1589
|
|
|
'title' => $this->user['username'], |
|
1590
|
|
|
'path' => $path, |
|
1591
|
|
|
'read_only' => false, |
|
1592
|
|
|
'user_mount' => true |
|
1593
|
|
|
]; |
|
1594
|
|
|
// Try and mount with only [uid] |
|
1595
|
|
|
$path = $userHomeFilter . $this->user['uid'] . $GLOBALS['TYPO3_CONF_VARS']['BE']['userUploadDir']; |
|
1596
|
|
|
$fileMountRecordCache[$userHomeStorageUid . $path] = [ |
|
1597
|
|
|
'base' => $userHomeStorageUid, |
|
1598
|
|
|
'title' => $this->user['username'], |
|
1599
|
|
|
'path' => $path, |
|
1600
|
|
|
'read_only' => false, |
|
1601
|
|
|
'user_mount' => true |
|
1602
|
|
|
]; |
|
1603
|
|
|
} |
|
1604
|
|
|
} |
|
1605
|
|
|
|
|
1606
|
|
|
// Mount group home-dirs |
|
1607
|
|
|
$mountOptions = new BackendGroupMountOption((int)$this->user['options']); |
|
1608
|
|
|
if ($GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'] !== '' && $mountOptions->shouldUserIncludeFileMountsFromAssociatedGroups()) { |
|
1609
|
|
|
// If groupHomePath is set, we attempt to mount it |
|
1610
|
|
|
[$groupHomeStorageUid, $groupHomeFilter] = explode(':', $GLOBALS['TYPO3_CONF_VARS']['BE']['groupHomePath'], 2); |
|
1611
|
|
|
$groupHomeStorageUid = (int)$groupHomeStorageUid; |
|
1612
|
|
|
$groupHomeFilter = '/' . ltrim($groupHomeFilter, '/'); |
|
1613
|
|
|
if ($groupHomeStorageUid > 0) { |
|
1614
|
|
|
foreach ($this->userGroups as $groupData) { |
|
1615
|
|
|
$path = $groupHomeFilter . $groupData['uid']; |
|
1616
|
|
|
$fileMountRecordCache[$groupHomeStorageUid . $path] = [ |
|
1617
|
|
|
'base' => $groupHomeStorageUid, |
|
1618
|
|
|
'title' => $groupData['title'], |
|
1619
|
|
|
'path' => $path, |
|
1620
|
|
|
'read_only' => false, |
|
1621
|
|
|
'user_mount' => true |
|
1622
|
|
|
]; |
|
1623
|
|
|
} |
|
1624
|
|
|
} |
|
1625
|
|
|
} |
|
1626
|
|
|
} |
|
1627
|
|
|
|
|
1628
|
|
|
$runtimeCache->set('backendUserAuthenticationFileMountRecords', $fileMountRecordCache); |
|
1629
|
|
|
return $fileMountRecordCache; |
|
1630
|
|
|
} |
|
1631
|
|
|
|
|
1632
|
|
|
/** |
|
1633
|
|
|
* Returns an array with the filemounts for the user. |
|
1634
|
|
|
* Each filemount is represented with an array of a "name", "path" and "type". |
|
1635
|
|
|
* If no filemounts an empty array is returned. |
|
1636
|
|
|
* |
|
1637
|
|
|
* @return \TYPO3\CMS\Core\Resource\ResourceStorage[] |
|
1638
|
|
|
*/ |
|
1639
|
|
|
public function getFileStorages() |
|
1640
|
|
|
{ |
|
1641
|
|
|
// Initializing file mounts after the groups are fetched |
|
1642
|
|
|
if ($this->fileStorages === null) { |
|
1643
|
|
|
$this->initializeFileStorages(); |
|
1644
|
|
|
} |
|
1645
|
|
|
return $this->fileStorages; |
|
1646
|
|
|
} |
|
1647
|
|
|
|
|
1648
|
|
|
/** |
|
1649
|
|
|
* Adds filters based on what the user has set |
|
1650
|
|
|
* this should be done in this place, and called whenever needed, |
|
1651
|
|
|
* but only when needed |
|
1652
|
|
|
*/ |
|
1653
|
|
|
public function evaluateUserSpecificFileFilterSettings() |
|
1654
|
|
|
{ |
|
1655
|
|
|
// Add the option for also displaying the non-hidden files |
|
1656
|
|
|
if ($this->uc['showHiddenFilesAndFolders'] ?? false) { |
|
1657
|
|
|
FileNameFilter::setShowHiddenFilesAndFolders(true); |
|
1658
|
|
|
} |
|
1659
|
|
|
} |
|
1660
|
|
|
|
|
1661
|
|
|
/** |
|
1662
|
|
|
* Returns the information about file permissions. |
|
1663
|
|
|
* Previously, this was stored in the DB field fileoper_perms now it is file_permissions. |
|
1664
|
|
|
* Besides it can be handled via userTSconfig |
|
1665
|
|
|
* |
|
1666
|
|
|
* permissions.file.default { |
|
1667
|
|
|
* addFile = 1 |
|
1668
|
|
|
* readFile = 1 |
|
1669
|
|
|
* writeFile = 1 |
|
1670
|
|
|
* copyFile = 1 |
|
1671
|
|
|
* moveFile = 1 |
|
1672
|
|
|
* renameFile = 1 |
|
1673
|
|
|
* deleteFile = 1 |
|
1674
|
|
|
* |
|
1675
|
|
|
* addFolder = 1 |
|
1676
|
|
|
* readFolder = 1 |
|
1677
|
|
|
* writeFolder = 1 |
|
1678
|
|
|
* copyFolder = 1 |
|
1679
|
|
|
* moveFolder = 1 |
|
1680
|
|
|
* renameFolder = 1 |
|
1681
|
|
|
* deleteFolder = 1 |
|
1682
|
|
|
* recursivedeleteFolder = 1 |
|
1683
|
|
|
* } |
|
1684
|
|
|
* |
|
1685
|
|
|
* # overwrite settings for a specific storageObject |
|
1686
|
|
|
* permissions.file.storage.StorageUid { |
|
1687
|
|
|
* readFile = 1 |
|
1688
|
|
|
* recursivedeleteFolder = 0 |
|
1689
|
|
|
* } |
|
1690
|
|
|
* |
|
1691
|
|
|
* Please note that these permissions only apply, if the storage has the |
|
1692
|
|
|
* capabilities (browseable, writable), and if the driver allows for writing etc |
|
1693
|
|
|
* |
|
1694
|
|
|
* @return array |
|
1695
|
|
|
*/ |
|
1696
|
|
|
public function getFilePermissions() |
|
1697
|
|
|
{ |
|
1698
|
|
|
if (!isset($this->filePermissions)) { |
|
1699
|
|
|
$filePermissions = [ |
|
1700
|
|
|
// File permissions |
|
1701
|
|
|
'addFile' => false, |
|
1702
|
|
|
'readFile' => false, |
|
1703
|
|
|
'writeFile' => false, |
|
1704
|
|
|
'copyFile' => false, |
|
1705
|
|
|
'moveFile' => false, |
|
1706
|
|
|
'renameFile' => false, |
|
1707
|
|
|
'deleteFile' => false, |
|
1708
|
|
|
// Folder permissions |
|
1709
|
|
|
'addFolder' => false, |
|
1710
|
|
|
'readFolder' => false, |
|
1711
|
|
|
'writeFolder' => false, |
|
1712
|
|
|
'copyFolder' => false, |
|
1713
|
|
|
'moveFolder' => false, |
|
1714
|
|
|
'renameFolder' => false, |
|
1715
|
|
|
'deleteFolder' => false, |
|
1716
|
|
|
'recursivedeleteFolder' => false |
|
1717
|
|
|
]; |
|
1718
|
|
|
if ($this->isAdmin()) { |
|
1719
|
|
|
$filePermissions = array_map('is_bool', $filePermissions); |
|
1720
|
|
|
} else { |
|
1721
|
|
|
$userGroupRecordPermissions = GeneralUtility::trimExplode(',', $this->groupData['file_permissions'] ?? '', true); |
|
1722
|
|
|
array_walk( |
|
1723
|
|
|
$userGroupRecordPermissions, |
|
1724
|
|
|
function ($permission) use (&$filePermissions) { |
|
1725
|
|
|
$filePermissions[$permission] = true; |
|
1726
|
|
|
} |
|
1727
|
|
|
); |
|
1728
|
|
|
|
|
1729
|
|
|
// Finally overlay any userTSconfig |
|
1730
|
|
|
$permissionsTsConfig = $this->getTSConfig()['permissions.']['file.']['default.'] ?? []; |
|
1731
|
|
|
if (!empty($permissionsTsConfig)) { |
|
1732
|
|
|
array_walk( |
|
1733
|
|
|
$permissionsTsConfig, |
|
1734
|
|
|
function ($value, $permission) use (&$filePermissions) { |
|
1735
|
|
|
$filePermissions[$permission] = (bool)$value; |
|
1736
|
|
|
} |
|
1737
|
|
|
); |
|
1738
|
|
|
} |
|
1739
|
|
|
} |
|
1740
|
|
|
$this->filePermissions = $filePermissions; |
|
1741
|
|
|
} |
|
1742
|
|
|
return $this->filePermissions; |
|
1743
|
|
|
} |
|
1744
|
|
|
|
|
1745
|
|
|
/** |
|
1746
|
|
|
* Gets the file permissions for a storage |
|
1747
|
|
|
* by merging any storage-specific permissions for a |
|
1748
|
|
|
* storage with the default settings. |
|
1749
|
|
|
* Admin users will always get the default settings. |
|
1750
|
|
|
* |
|
1751
|
|
|
* @param \TYPO3\CMS\Core\Resource\ResourceStorage $storageObject |
|
1752
|
|
|
* @return array |
|
1753
|
|
|
*/ |
|
1754
|
|
|
public function getFilePermissionsForStorage(ResourceStorage $storageObject) |
|
1755
|
|
|
{ |
|
1756
|
|
|
$finalUserPermissions = $this->getFilePermissions(); |
|
1757
|
|
|
if (!$this->isAdmin()) { |
|
1758
|
|
|
$storageFilePermissions = $this->getTSConfig()['permissions.']['file.']['storage.'][$storageObject->getUid() . '.'] ?? []; |
|
1759
|
|
|
if (!empty($storageFilePermissions)) { |
|
1760
|
|
|
array_walk( |
|
1761
|
|
|
$storageFilePermissions, |
|
1762
|
|
|
function ($value, $permission) use (&$finalUserPermissions) { |
|
1763
|
|
|
$finalUserPermissions[$permission] = (bool)$value; |
|
1764
|
|
|
} |
|
1765
|
|
|
); |
|
1766
|
|
|
} |
|
1767
|
|
|
} |
|
1768
|
|
|
return $finalUserPermissions; |
|
1769
|
|
|
} |
|
1770
|
|
|
|
|
1771
|
|
|
/** |
|
1772
|
|
|
* Returns a \TYPO3\CMS\Core\Resource\Folder object that is used for uploading |
|
1773
|
|
|
* files by default. |
|
1774
|
|
|
* This is used for RTE and its magic images, as well as uploads |
|
1775
|
|
|
* in the TCEforms fields. |
|
1776
|
|
|
* |
|
1777
|
|
|
* The default upload folder for a user is the defaultFolder on the first |
|
1778
|
|
|
* filestorage/filemount that the user can access and to which files are allowed to be added |
|
1779
|
|
|
* however, you can set the users' upload folder like this: |
|
1780
|
|
|
* |
|
1781
|
|
|
* options.defaultUploadFolder = 3:myfolder/yourfolder/ |
|
1782
|
|
|
* |
|
1783
|
|
|
* @param int $pid PageUid |
|
1784
|
|
|
* @param string $table Table name |
|
1785
|
|
|
* @param string $field Field name |
|
1786
|
|
|
* @return \TYPO3\CMS\Core\Resource\Folder|bool The default upload folder for this user |
|
1787
|
|
|
*/ |
|
1788
|
|
|
public function getDefaultUploadFolder($pid = null, $table = null, $field = null) |
|
1789
|
|
|
{ |
|
1790
|
|
|
$uploadFolder = $this->getTSConfig()['options.']['defaultUploadFolder'] ?? ''; |
|
1791
|
|
|
if ($uploadFolder) { |
|
1792
|
|
|
try { |
|
1793
|
|
|
$uploadFolder = GeneralUtility::makeInstance(ResourceFactory::class)->getFolderObjectFromCombinedIdentifier($uploadFolder); |
|
1794
|
|
|
} catch (Exception\FolderDoesNotExistException $e) { |
|
1795
|
|
|
$uploadFolder = null; |
|
1796
|
|
|
} |
|
1797
|
|
|
} |
|
1798
|
|
|
if (empty($uploadFolder)) { |
|
1799
|
|
|
foreach ($this->getFileStorages() as $storage) { |
|
1800
|
|
|
if ($storage->isDefault() && $storage->isWritable()) { |
|
1801
|
|
|
try { |
|
1802
|
|
|
$uploadFolder = $storage->getDefaultFolder(); |
|
1803
|
|
|
if ($uploadFolder->checkActionPermission('write')) { |
|
1804
|
|
|
break; |
|
1805
|
|
|
} |
|
1806
|
|
|
$uploadFolder = null; |
|
1807
|
|
|
} catch (Exception $folderAccessException) { |
|
1808
|
|
|
// If the folder is not accessible (no permissions / does not exist) we skip this one. |
|
1809
|
|
|
} |
|
1810
|
|
|
break; |
|
1811
|
|
|
} |
|
1812
|
|
|
} |
|
1813
|
|
|
if (!$uploadFolder instanceof Folder) { |
|
1814
|
|
|
/** @var ResourceStorage $storage */ |
|
1815
|
|
|
foreach ($this->getFileStorages() as $storage) { |
|
1816
|
|
|
if ($storage->isWritable()) { |
|
1817
|
|
|
try { |
|
1818
|
|
|
$uploadFolder = $storage->getDefaultFolder(); |
|
1819
|
|
|
if ($uploadFolder->checkActionPermission('write')) { |
|
1820
|
|
|
break; |
|
1821
|
|
|
} |
|
1822
|
|
|
$uploadFolder = null; |
|
1823
|
|
|
} catch (Exception $folderAccessException) { |
|
1824
|
|
|
// If the folder is not accessible (no permissions / does not exist) try the next one. |
|
1825
|
|
|
} |
|
1826
|
|
|
} |
|
1827
|
|
|
} |
|
1828
|
|
|
} |
|
1829
|
|
|
} |
|
1830
|
|
|
|
|
1831
|
|
|
// HOOK: getDefaultUploadFolder |
|
1832
|
|
|
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['getDefaultUploadFolder'] ?? [] as $_funcRef) { |
|
1833
|
|
|
$_params = [ |
|
1834
|
|
|
'uploadFolder' => $uploadFolder, |
|
1835
|
|
|
'pid' => $pid, |
|
1836
|
|
|
'table' => $table, |
|
1837
|
|
|
'field' => $field, |
|
1838
|
|
|
]; |
|
1839
|
|
|
$uploadFolder = GeneralUtility::callUserFunction($_funcRef, $_params, $this); |
|
1840
|
|
|
} |
|
1841
|
|
|
|
|
1842
|
|
|
if ($uploadFolder instanceof Folder) { |
|
1843
|
|
|
return $uploadFolder; |
|
1844
|
|
|
} |
|
1845
|
|
|
return false; |
|
1846
|
|
|
} |
|
1847
|
|
|
|
|
1848
|
|
|
/** |
|
1849
|
|
|
* Returns a \TYPO3\CMS\Core\Resource\Folder object that could be used for uploading |
|
1850
|
|
|
* temporary files in user context. The folder _temp_ below the default upload folder |
|
1851
|
|
|
* of the user is used. |
|
1852
|
|
|
* |
|
1853
|
|
|
* @return \TYPO3\CMS\Core\Resource\Folder|null |
|
1854
|
|
|
* @see \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::getDefaultUploadFolder() |
|
1855
|
|
|
*/ |
|
1856
|
|
|
public function getDefaultUploadTemporaryFolder() |
|
1857
|
|
|
{ |
|
1858
|
|
|
$defaultTemporaryFolder = null; |
|
1859
|
|
|
$defaultFolder = $this->getDefaultUploadFolder(); |
|
1860
|
|
|
|
|
1861
|
|
|
if ($defaultFolder !== false) { |
|
1862
|
|
|
$tempFolderName = '_temp_'; |
|
1863
|
|
|
$createFolder = !$defaultFolder->hasFolder($tempFolderName); |
|
1864
|
|
|
if ($createFolder === true) { |
|
1865
|
|
|
try { |
|
1866
|
|
|
$defaultTemporaryFolder = $defaultFolder->createFolder($tempFolderName); |
|
1867
|
|
|
} catch (Exception $folderAccessException) { |
|
|
|
|
|
|
1868
|
|
|
} |
|
1869
|
|
|
} else { |
|
1870
|
|
|
$defaultTemporaryFolder = $defaultFolder->getSubfolder($tempFolderName); |
|
1871
|
|
|
} |
|
1872
|
|
|
} |
|
1873
|
|
|
|
|
1874
|
|
|
return $defaultTemporaryFolder; |
|
1875
|
|
|
} |
|
1876
|
|
|
|
|
1877
|
|
|
/** |
|
1878
|
|
|
* Initializing workspace. |
|
1879
|
|
|
* Called from within this function, see fetchGroupData() |
|
1880
|
|
|
* |
|
1881
|
|
|
* @see fetchGroupData() |
|
1882
|
|
|
* @internal should only be used from within TYPO3 Core |
|
1883
|
|
|
*/ |
|
1884
|
|
|
public function workspaceInit() |
|
1885
|
|
|
{ |
|
1886
|
|
|
// Initializing workspace by evaluating and setting the workspace, possibly updating it in the user record! |
|
1887
|
|
|
$this->setWorkspace($this->user['workspace_id']); |
|
1888
|
|
|
// Limiting the DB mountpoints if there any selected in the workspace record |
|
1889
|
|
|
$this->initializeDbMountpointsInWorkspace(); |
|
1890
|
|
|
$allowed_languages = (string)($this->getTSConfig()['options.']['workspaces.']['allowed_languages.'][$this->workspace] ?? ''); |
|
1891
|
|
|
if ($allowed_languages !== '') { |
|
1892
|
|
|
$this->groupData['allowed_languages'] = StringUtility::uniqueList($allowed_languages); |
|
1893
|
|
|
} |
|
1894
|
|
|
} |
|
1895
|
|
|
|
|
1896
|
|
|
/** |
|
1897
|
|
|
* Limiting the DB mountpoints if there any selected in the workspace record |
|
1898
|
|
|
*/ |
|
1899
|
|
|
protected function initializeDbMountpointsInWorkspace() |
|
1900
|
|
|
{ |
|
1901
|
|
|
$dbMountpoints = trim($this->workspaceRec['db_mountpoints'] ?? ''); |
|
1902
|
|
|
if ($this->workspace > 0 && $dbMountpoints != '') { |
|
1903
|
|
|
$filteredDbMountpoints = []; |
|
1904
|
|
|
// Notice: We cannot call $this->getPagePermsClause(1); |
|
1905
|
|
|
// as usual because the group-list is not available at this point. |
|
1906
|
|
|
// But bypassing is fine because all we want here is check if the |
|
1907
|
|
|
// workspace mounts are inside the current webmounts rootline. |
|
1908
|
|
|
// The actual permission checking on page level is done elsewhere |
|
1909
|
|
|
// as usual anyway before the page tree is rendered. |
|
1910
|
|
|
$readPerms = '1=1'; |
|
1911
|
|
|
// Traverse mount points of the workspace, add them, |
|
1912
|
|
|
// but make sure they match against the users' DB mounts |
|
1913
|
|
|
|
|
1914
|
|
|
$workspaceWebMounts = GeneralUtility::intExplode(',', $dbMountpoints); |
|
1915
|
|
|
$webMountsOfUser = GeneralUtility::intExplode(',', $this->groupData['webmounts']); |
|
1916
|
|
|
$webMountsOfUser = array_combine($webMountsOfUser, $webMountsOfUser) ?: []; |
|
1917
|
|
|
|
|
1918
|
|
|
$entryPointRootLineUids = []; |
|
1919
|
|
|
foreach ($webMountsOfUser as $webMountPageId) { |
|
1920
|
|
|
$rootLine = BackendUtility::BEgetRootLine($webMountPageId, '', true); |
|
1921
|
|
|
$entryPointRootLineUids[$webMountPageId] = array_map('intval', array_column($rootLine, 'uid')); |
|
1922
|
|
|
} |
|
1923
|
|
|
foreach ($entryPointRootLineUids as $webMountOfUser => $uidsOfRootLine) { |
|
1924
|
|
|
// Remove the DB mounts of the user if the DB mount is not in the list of |
|
1925
|
|
|
// workspace mounts |
|
1926
|
|
|
foreach ($workspaceWebMounts as $webmountOfWorkspace) { |
|
1927
|
|
|
// This workspace DB mount is somewhere in the rootline of the users' web mount, |
|
1928
|
|
|
// so this is "OK" to be included |
|
1929
|
|
|
if (in_array($webmountOfWorkspace, $uidsOfRootLine, true)) { |
|
1930
|
|
|
continue; |
|
1931
|
|
|
} |
|
1932
|
|
|
// Remove the user's DB Mount (possible via array_combine, see above) |
|
1933
|
|
|
unset($webMountsOfUser[$webMountOfUser]); |
|
1934
|
|
|
} |
|
1935
|
|
|
} |
|
1936
|
|
|
$dbMountpoints = array_merge($workspaceWebMounts, $webMountsOfUser); |
|
1937
|
|
|
$dbMountpoints = array_unique($dbMountpoints); |
|
1938
|
|
|
foreach ($dbMountpoints as $mpId) { |
|
1939
|
|
|
if ($this->isInWebMount($mpId, $readPerms)) { |
|
|
|
|
|
|
1940
|
|
|
$filteredDbMountpoints[] = $mpId; |
|
1941
|
|
|
} |
|
1942
|
|
|
} |
|
1943
|
|
|
// Re-insert webmounts |
|
1944
|
|
|
$this->groupData['webmounts'] = implode(',', $filteredDbMountpoints); |
|
1945
|
|
|
} |
|
1946
|
|
|
} |
|
1947
|
|
|
|
|
1948
|
|
|
/** |
|
1949
|
|
|
* Checking if a workspace is allowed for backend user |
|
1950
|
|
|
* |
|
1951
|
|
|
* @param mixed $wsRec If integer, workspace record is looked up, if array it is seen as a Workspace record with at least uid, title, members and adminusers columns. Can be faked for workspaces uid 0 and -1 (online and offline) |
|
1952
|
|
|
* @param string $fields List of fields to select. Default fields are all |
|
1953
|
|
|
* @return array Output will also show how access was granted. Admin users will have a true output regardless of input. |
|
1954
|
|
|
* @internal should only be used from within TYPO3 Core |
|
1955
|
|
|
*/ |
|
1956
|
|
|
public function checkWorkspace($wsRec, $fields = '*') |
|
1957
|
|
|
{ |
|
1958
|
|
|
$retVal = false; |
|
1959
|
|
|
// If not array, look up workspace record: |
|
1960
|
|
|
if (!is_array($wsRec)) { |
|
1961
|
|
|
switch ((string)$wsRec) { |
|
1962
|
|
|
case '0': |
|
1963
|
|
|
$wsRec = ['uid' => $wsRec]; |
|
1964
|
|
|
break; |
|
1965
|
|
|
default: |
|
1966
|
|
|
if (ExtensionManagementUtility::isLoaded('workspaces')) { |
|
1967
|
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace'); |
|
1968
|
|
|
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class)); |
|
1969
|
|
|
$wsRec = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields)) |
|
1970
|
|
|
->from('sys_workspace') |
|
1971
|
|
|
->where($queryBuilder->expr()->eq( |
|
1972
|
|
|
'uid', |
|
1973
|
|
|
$queryBuilder->createNamedParameter($wsRec, \PDO::PARAM_INT) |
|
1974
|
|
|
)) |
|
1975
|
|
|
->orderBy('title') |
|
1976
|
|
|
->setMaxResults(1) |
|
1977
|
|
|
->execute() |
|
1978
|
|
|
->fetch(\PDO::FETCH_ASSOC); |
|
1979
|
|
|
} |
|
1980
|
|
|
} |
|
1981
|
|
|
} |
|
1982
|
|
|
// If wsRec is set to an array, evaluate it: |
|
1983
|
|
|
if (is_array($wsRec)) { |
|
1984
|
|
|
if ($this->isAdmin()) { |
|
1985
|
|
|
return array_merge($wsRec, ['_ACCESS' => 'admin']); |
|
1986
|
|
|
} |
|
1987
|
|
|
switch ((string)$wsRec['uid']) { |
|
1988
|
|
|
case '0': |
|
1989
|
|
|
$retVal = (($this->groupData['workspace_perms'] ?? 0) & 1) |
|
1990
|
|
|
? array_merge($wsRec, ['_ACCESS' => 'online']) |
|
1991
|
|
|
: false; |
|
1992
|
|
|
break; |
|
1993
|
|
|
default: |
|
1994
|
|
|
// Checking if the guy is admin: |
|
1995
|
|
|
if (GeneralUtility::inList($wsRec['adminusers'], 'be_users_' . $this->user['uid'])) { |
|
1996
|
|
|
return array_merge($wsRec, ['_ACCESS' => 'owner']); |
|
1997
|
|
|
} |
|
1998
|
|
|
// Checking if he is owner through a user group of his: |
|
1999
|
|
|
foreach ($this->userGroupsUID as $groupUid) { |
|
2000
|
|
|
if (GeneralUtility::inList($wsRec['adminusers'], 'be_groups_' . $groupUid)) { |
|
2001
|
|
|
return array_merge($wsRec, ['_ACCESS' => 'owner']); |
|
2002
|
|
|
} |
|
2003
|
|
|
} |
|
2004
|
|
|
// Checking if he is member as user: |
|
2005
|
|
|
if (GeneralUtility::inList($wsRec['members'], 'be_users_' . $this->user['uid'])) { |
|
2006
|
|
|
return array_merge($wsRec, ['_ACCESS' => 'member']); |
|
2007
|
|
|
} |
|
2008
|
|
|
// Checking if he is member through a user group of his: |
|
2009
|
|
|
foreach ($this->userGroupsUID as $groupUid) { |
|
2010
|
|
|
if (GeneralUtility::inList($wsRec['members'], 'be_groups_' . $groupUid)) { |
|
2011
|
|
|
return array_merge($wsRec, ['_ACCESS' => 'member']); |
|
2012
|
|
|
} |
|
2013
|
|
|
} |
|
2014
|
|
|
} |
|
2015
|
|
|
} |
|
2016
|
|
|
return $retVal; |
|
|
|
|
|
|
2017
|
|
|
} |
|
2018
|
|
|
|
|
2019
|
|
|
/** |
|
2020
|
|
|
* Uses checkWorkspace() to check if current workspace is available for user. |
|
2021
|
|
|
* This function caches the result and so can be called many times with no performance loss. |
|
2022
|
|
|
* |
|
2023
|
|
|
* @return array See checkWorkspace() |
|
2024
|
|
|
* @see checkWorkspace() |
|
2025
|
|
|
* @internal should only be used from within TYPO3 Core |
|
2026
|
|
|
*/ |
|
2027
|
|
|
public function checkWorkspaceCurrent() |
|
2028
|
|
|
{ |
|
2029
|
|
|
if (!isset($this->checkWorkspaceCurrent_cache)) { |
|
2030
|
|
|
$this->checkWorkspaceCurrent_cache = $this->checkWorkspace($this->workspace); |
|
2031
|
|
|
} |
|
2032
|
|
|
return $this->checkWorkspaceCurrent_cache; |
|
2033
|
|
|
} |
|
2034
|
|
|
|
|
2035
|
|
|
/** |
|
2036
|
|
|
* Setting workspace ID |
|
2037
|
|
|
* |
|
2038
|
|
|
* @param int $workspaceId ID of workspace to set for backend user. If not valid the default workspace for BE user is found and set. |
|
2039
|
|
|
* @internal should only be used from within TYPO3 Core |
|
2040
|
|
|
*/ |
|
2041
|
|
|
public function setWorkspace($workspaceId) |
|
2042
|
|
|
{ |
|
2043
|
|
|
// Check workspace validity and if not found, revert to default workspace. |
|
2044
|
|
|
if (!$this->setTemporaryWorkspace($workspaceId)) { |
|
2045
|
|
|
$this->setDefaultWorkspace(); |
|
2046
|
|
|
} |
|
2047
|
|
|
// Unset access cache: |
|
2048
|
|
|
$this->checkWorkspaceCurrent_cache = null; |
|
2049
|
|
|
// If ID is different from the stored one, change it: |
|
2050
|
|
|
if ((int)$this->workspace !== (int)$this->user['workspace_id']) { |
|
2051
|
|
|
$this->user['workspace_id'] = $this->workspace; |
|
2052
|
|
|
GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users')->update( |
|
2053
|
|
|
'be_users', |
|
2054
|
|
|
['workspace_id' => $this->user['workspace_id']], |
|
2055
|
|
|
['uid' => (int)$this->user['uid']] |
|
2056
|
|
|
); |
|
2057
|
|
|
$this->writelog(SystemLogType::EXTENSION, SystemLogGenericAction::UNDEFINED, SystemLogErrorClassification::MESSAGE, 0, 'User changed workspace to "' . $this->workspace . '"', []); |
|
2058
|
|
|
} |
|
2059
|
|
|
} |
|
2060
|
|
|
|
|
2061
|
|
|
/** |
|
2062
|
|
|
* Sets a temporary workspace in the context of the current backend user. |
|
2063
|
|
|
* |
|
2064
|
|
|
* @param int $workspaceId |
|
2065
|
|
|
* @return bool |
|
2066
|
|
|
* @internal should only be used from within TYPO3 Core |
|
2067
|
|
|
*/ |
|
2068
|
|
|
public function setTemporaryWorkspace($workspaceId) |
|
2069
|
|
|
{ |
|
2070
|
|
|
$result = false; |
|
2071
|
|
|
$workspaceRecord = $this->checkWorkspace($workspaceId); |
|
2072
|
|
|
|
|
2073
|
|
|
if ($workspaceRecord) { |
|
|
|
|
|
|
2074
|
|
|
$this->workspaceRec = $workspaceRecord; |
|
2075
|
|
|
$this->workspace = (int)$workspaceId; |
|
2076
|
|
|
$result = true; |
|
2077
|
|
|
} |
|
2078
|
|
|
|
|
2079
|
|
|
return $result; |
|
2080
|
|
|
} |
|
2081
|
|
|
|
|
2082
|
|
|
/** |
|
2083
|
|
|
* Sets the default workspace in the context of the current backend user. |
|
2084
|
|
|
* @internal should only be used from within TYPO3 Core |
|
2085
|
|
|
*/ |
|
2086
|
|
|
public function setDefaultWorkspace() |
|
2087
|
|
|
{ |
|
2088
|
|
|
$this->workspace = (int)$this->getDefaultWorkspace(); |
|
2089
|
|
|
$this->workspaceRec = $this->checkWorkspace($this->workspace); |
|
2090
|
|
|
} |
|
2091
|
|
|
|
|
2092
|
|
|
/** |
|
2093
|
|
|
* Return default workspace ID for user, |
|
2094
|
|
|
* if EXT:workspaces is not installed the user will be pushed to the |
|
2095
|
|
|
* Live workspace, if he has access to. If no workspace is available for the user, the workspace ID is set to "-99" |
|
2096
|
|
|
* |
|
2097
|
|
|
* @return int Default workspace id. |
|
2098
|
|
|
* @internal should only be used from within TYPO3 Core |
|
2099
|
|
|
*/ |
|
2100
|
|
|
public function getDefaultWorkspace() |
|
2101
|
|
|
{ |
|
2102
|
|
|
if (!ExtensionManagementUtility::isLoaded('workspaces')) { |
|
2103
|
|
|
return 0; |
|
2104
|
|
|
} |
|
2105
|
|
|
// Online is default |
|
2106
|
|
|
if ($this->checkWorkspace(0)) { |
|
2107
|
|
|
return 0; |
|
2108
|
|
|
} |
|
2109
|
|
|
// Otherwise -99 is the fallback |
|
2110
|
|
|
$defaultWorkspace = -99; |
|
2111
|
|
|
// Traverse all workspaces |
|
2112
|
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace'); |
|
2113
|
|
|
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class)); |
|
2114
|
|
|
$result = $queryBuilder->select('*') |
|
2115
|
|
|
->from('sys_workspace') |
|
2116
|
|
|
->orderBy('title') |
|
2117
|
|
|
->execute(); |
|
2118
|
|
|
while ($workspaceRecord = $result->fetch()) { |
|
2119
|
|
|
if ($this->checkWorkspace($workspaceRecord)) { |
|
2120
|
|
|
$defaultWorkspace = (int)$workspaceRecord['uid']; |
|
2121
|
|
|
break; |
|
2122
|
|
|
} |
|
2123
|
|
|
} |
|
2124
|
|
|
return $defaultWorkspace; |
|
2125
|
|
|
} |
|
2126
|
|
|
|
|
2127
|
|
|
/** |
|
2128
|
|
|
* Writes an entry in the logfile/table |
|
2129
|
|
|
* Documentation in "TYPO3 Core API" |
|
2130
|
|
|
* |
|
2131
|
|
|
* @param int $type Denotes which module that has submitted the entry. See "TYPO3 Core API". Use "4" for extensions. |
|
2132
|
|
|
* @param int $action Denotes which specific operation that wrote the entry. Use "0" when no sub-categorizing applies |
|
2133
|
|
|
* @param int $error Flag. 0 = message, 1 = error (user problem), 2 = System Error (which should not happen), 3 = security notice (admin) |
|
2134
|
|
|
* @param int $details_nr The message number. Specific for each $type and $action. This will make it possible to translate errormessages to other languages |
|
2135
|
|
|
* @param string $details Default text that follows the message (in english!). Possibly translated by identification through type/action/details_nr |
|
2136
|
|
|
* @param array $data Data that follows the log. Might be used to carry special information. If an array the first 5 entries (0-4) will be sprintf'ed with the details-text |
|
2137
|
|
|
* @param string $tablename Table name. Special field used by tce_main.php. |
|
2138
|
|
|
* @param int|string $recuid Record UID. Special field used by tce_main.php. |
|
2139
|
|
|
* @param int|string $recpid Record PID. Special field used by tce_main.php. OBSOLETE |
|
2140
|
|
|
* @param int $event_pid The page_uid (pid) where the event occurred. Used to select log-content for specific pages. |
|
2141
|
|
|
* @param string $NEWid Special field used by tce_main.php. NEWid string of newly created records. |
|
2142
|
|
|
* @param int $userId Alternative Backend User ID (used for logging login actions where this is not yet known). |
|
2143
|
|
|
* @return int Log entry ID. |
|
2144
|
|
|
*/ |
|
2145
|
|
|
public function writelog($type, $action, $error, $details_nr, $details, $data, $tablename = '', $recuid = '', $recpid = '', $event_pid = -1, $NEWid = '', $userId = 0) |
|
2146
|
|
|
{ |
|
2147
|
|
|
if (!$userId && !empty($this->user['uid'])) { |
|
2148
|
|
|
$userId = $this->user['uid']; |
|
2149
|
|
|
} |
|
2150
|
|
|
|
|
2151
|
|
|
if ($backuserid = $this->getOriginalUserIdWhenInSwitchUserMode()) { |
|
2152
|
|
|
if (empty($data)) { |
|
2153
|
|
|
$data = []; |
|
2154
|
|
|
} |
|
2155
|
|
|
$data['originalUser'] = $backuserid; |
|
2156
|
|
|
} |
|
2157
|
|
|
|
|
2158
|
|
|
// @todo Remove this once this method is properly typed. |
|
2159
|
|
|
$type = (int)$type; |
|
2160
|
|
|
|
|
2161
|
|
|
$fields = [ |
|
2162
|
|
|
'userid' => (int)$userId, |
|
2163
|
|
|
'type' => $type, |
|
2164
|
|
|
'channel' => Type::toChannel($type), |
|
2165
|
|
|
'level' => Type::toLevel($type), |
|
2166
|
|
|
'action' => (int)$action, |
|
2167
|
|
|
'error' => (int)$error, |
|
2168
|
|
|
'details_nr' => (int)$details_nr, |
|
2169
|
|
|
'details' => $details, |
|
2170
|
|
|
'log_data' => serialize($data), |
|
2171
|
|
|
'tablename' => $tablename, |
|
2172
|
|
|
'recuid' => (int)$recuid, |
|
2173
|
|
|
'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'), |
|
2174
|
|
|
'tstamp' => $GLOBALS['EXEC_TIME'] ?? time(), |
|
2175
|
|
|
'event_pid' => (int)$event_pid, |
|
2176
|
|
|
'NEWid' => $NEWid, |
|
2177
|
|
|
'workspace' => $this->workspace |
|
2178
|
|
|
]; |
|
2179
|
|
|
|
|
2180
|
|
|
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_log'); |
|
2181
|
|
|
$connection->insert( |
|
2182
|
|
|
'sys_log', |
|
2183
|
|
|
$fields, |
|
2184
|
|
|
[ |
|
2185
|
|
|
\PDO::PARAM_INT, |
|
2186
|
|
|
\PDO::PARAM_INT, |
|
2187
|
|
|
\PDO::PARAM_STR, |
|
2188
|
|
|
\PDO::PARAM_STR, |
|
2189
|
|
|
\PDO::PARAM_INT, |
|
2190
|
|
|
\PDO::PARAM_INT, |
|
2191
|
|
|
\PDO::PARAM_INT, |
|
2192
|
|
|
\PDO::PARAM_STR, |
|
2193
|
|
|
\PDO::PARAM_STR, |
|
2194
|
|
|
\PDO::PARAM_STR, |
|
2195
|
|
|
\PDO::PARAM_INT, |
|
2196
|
|
|
\PDO::PARAM_STR, |
|
2197
|
|
|
\PDO::PARAM_INT, |
|
2198
|
|
|
\PDO::PARAM_INT, |
|
2199
|
|
|
\PDO::PARAM_STR, |
|
2200
|
|
|
\PDO::PARAM_STR, |
|
2201
|
|
|
] |
|
2202
|
|
|
); |
|
2203
|
|
|
|
|
2204
|
|
|
return (int)$connection->lastInsertId('sys_log'); |
|
2205
|
|
|
} |
|
2206
|
|
|
|
|
2207
|
|
|
/** |
|
2208
|
|
|
* Getter for the cookie name |
|
2209
|
|
|
* |
|
2210
|
|
|
* @static |
|
2211
|
|
|
* @return string returns the configured cookie name |
|
2212
|
|
|
*/ |
|
2213
|
|
|
public static function getCookieName() |
|
2214
|
|
|
{ |
|
2215
|
|
|
$configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']); |
|
2216
|
|
|
if (empty($configuredCookieName)) { |
|
2217
|
|
|
$configuredCookieName = 'be_typo_user'; |
|
2218
|
|
|
} |
|
2219
|
|
|
return $configuredCookieName; |
|
2220
|
|
|
} |
|
2221
|
|
|
|
|
2222
|
|
|
/** |
|
2223
|
|
|
* Check if user is logged in and if so, call ->fetchGroupData() to load group information and |
|
2224
|
|
|
* access lists of all kind, further check IP, set the ->uc array. |
|
2225
|
|
|
* If no user is logged in the default behaviour is to exit with an error message. |
|
2226
|
|
|
* This function is called right after ->start() in fx. the TYPO3 Bootstrap. |
|
2227
|
|
|
* |
|
2228
|
|
|
* @param bool $proceedIfNoUserIsLoggedIn if this option is set, then there won't be a redirect to the login screen of the Backend - used for areas in the backend which do not need user rights like the login page. |
|
2229
|
|
|
* @throws \RuntimeException |
|
2230
|
|
|
* @todo deprecate |
|
2231
|
|
|
*/ |
|
2232
|
|
|
public function backendCheckLogin($proceedIfNoUserIsLoggedIn = false) |
|
2233
|
|
|
{ |
|
2234
|
|
|
if (empty($this->user['uid'])) { |
|
2235
|
|
|
if ($proceedIfNoUserIsLoggedIn === false) { |
|
2236
|
|
|
$url = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir; |
|
2237
|
|
|
throw new ImmediateResponseException(new RedirectResponse($url, 303), 1607271747); |
|
2238
|
|
|
} |
|
2239
|
|
|
} else { |
|
2240
|
|
|
if ($this->isUserAllowedToLogin()) { |
|
2241
|
|
|
$this->initializeBackendLogin(); |
|
2242
|
|
|
} else { |
|
2243
|
|
|
throw new \RuntimeException('Login Error: TYPO3 is in maintenance mode at the moment. Only administrators are allowed access.', 1294585860); |
|
2244
|
|
|
} |
|
2245
|
|
|
} |
|
2246
|
|
|
} |
|
2247
|
|
|
|
|
2248
|
|
|
/** |
|
2249
|
|
|
* @internal |
|
2250
|
|
|
*/ |
|
2251
|
|
|
public function initializeBackendLogin(): void |
|
2252
|
|
|
{ |
|
2253
|
|
|
// The groups are fetched and ready for permission checking in this initialization. |
|
2254
|
|
|
// Tables.php must be read before this because stuff like the modules has impact in this |
|
2255
|
|
|
$this->fetchGroupData(); |
|
2256
|
|
|
// Setting the UC array. It's needed with fetchGroupData first, due to default/overriding of values. |
|
2257
|
|
|
$this->backendSetUC(); |
|
2258
|
|
|
if ($this->loginSessionStarted) { |
|
2259
|
|
|
// Also, if there is a recovery link set, unset it now |
|
2260
|
|
|
// this will be moved into its own Event at a later stage. |
|
2261
|
|
|
// If a token was set previously, this is now unset, as it was now possible to log-in |
|
2262
|
|
|
if ($this->user['password_reset_token'] ?? '') { |
|
2263
|
|
|
GeneralUtility::makeInstance(ConnectionPool::class) |
|
2264
|
|
|
->getConnectionForTable($this->user_table) |
|
2265
|
|
|
->update($this->user_table, ['password_reset_token' => ''], ['uid' => $this->user['uid']]); |
|
2266
|
|
|
} |
|
2267
|
|
|
// Process hooks |
|
2268
|
|
|
$hooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauthgroup.php']['backendUserLogin']; |
|
2269
|
|
|
foreach ($hooks ?? [] as $_funcRef) { |
|
2270
|
|
|
$_params = ['user' => $this->user]; |
|
2271
|
|
|
GeneralUtility::callUserFunction($_funcRef, $_params, $this); |
|
2272
|
|
|
} |
|
2273
|
|
|
} |
|
2274
|
|
|
} |
|
2275
|
|
|
|
|
2276
|
|
|
/** |
|
2277
|
|
|
* Initialize the internal ->uc array for the backend user |
|
2278
|
|
|
* Will make the overrides if necessary, and write the UC back to the be_users record if changes has happened |
|
2279
|
|
|
* |
|
2280
|
|
|
* @internal |
|
2281
|
|
|
*/ |
|
2282
|
|
|
public function backendSetUC() |
|
2283
|
|
|
{ |
|
2284
|
|
|
// UC - user configuration is a serialized array inside the user object |
|
2285
|
|
|
// If there is a saved uc we implement that instead of the default one. |
|
2286
|
|
|
$this->unpack_uc(); |
|
2287
|
|
|
// Setting defaults if uc is empty |
|
2288
|
|
|
$updated = false; |
|
2289
|
|
|
if (!is_array($this->uc)) { |
|
2290
|
|
|
$this->uc = array_merge( |
|
2291
|
|
|
$this->uc_default, |
|
2292
|
|
|
(array)$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUC'], |
|
2293
|
|
|
GeneralUtility::removeDotsFromTS((array)($this->getTSConfig()['setup.']['default.'] ?? [])) |
|
2294
|
|
|
); |
|
2295
|
|
|
$this->overrideUC(); |
|
2296
|
|
|
$updated = true; |
|
2297
|
|
|
} |
|
2298
|
|
|
// If TSconfig is updated, update the defaultUC. |
|
2299
|
|
|
if ($this->userTSUpdated) { |
|
2300
|
|
|
$this->overrideUC(); |
|
2301
|
|
|
$updated = true; |
|
2302
|
|
|
} |
|
2303
|
|
|
// Setting default lang from be_user record, also update for backwards-compatibility |
|
2304
|
|
|
// @deprecated This will be removed in TYPO3 v12 |
|
2305
|
|
|
if (!isset($this->uc['lang']) || $this->uc['lang'] !== $this->user['lang']) { |
|
2306
|
|
|
$this->uc['lang'] = $this->user['lang']; |
|
2307
|
|
|
$updated = true; |
|
2308
|
|
|
} |
|
2309
|
|
|
// Setting the time of the first login: |
|
2310
|
|
|
if (!isset($this->uc['firstLoginTimeStamp'])) { |
|
2311
|
|
|
$this->uc['firstLoginTimeStamp'] = $GLOBALS['EXEC_TIME']; |
|
2312
|
|
|
$updated = true; |
|
2313
|
|
|
} |
|
2314
|
|
|
// Saving if updated. |
|
2315
|
|
|
if ($updated) { |
|
2316
|
|
|
$this->writeUC(); |
|
2317
|
|
|
} |
|
2318
|
|
|
} |
|
2319
|
|
|
|
|
2320
|
|
|
/** |
|
2321
|
|
|
* Override: Call this function every time the uc is updated. |
|
2322
|
|
|
* That is 1) by reverting to default values, 2) in the setup-module, 3) userTS changes (userauthgroup) |
|
2323
|
|
|
* |
|
2324
|
|
|
* @internal |
|
2325
|
|
|
*/ |
|
2326
|
|
|
public function overrideUC() |
|
2327
|
|
|
{ |
|
2328
|
|
|
$this->uc = array_merge((array)$this->uc, (array)($this->getTSConfig()['setup.']['override.'] ?? [])); |
|
2329
|
|
|
} |
|
2330
|
|
|
|
|
2331
|
|
|
/** |
|
2332
|
|
|
* Clears the user[uc] and ->uc to blank strings. Then calls ->backendSetUC() to fill it again with reset contents |
|
2333
|
|
|
* |
|
2334
|
|
|
* @internal |
|
2335
|
|
|
*/ |
|
2336
|
|
|
public function resetUC() |
|
2337
|
|
|
{ |
|
2338
|
|
|
$this->user['uc'] = ''; |
|
2339
|
|
|
$this->uc = ''; |
|
2340
|
|
|
$this->backendSetUC(); |
|
2341
|
|
|
} |
|
2342
|
|
|
|
|
2343
|
|
|
/** |
|
2344
|
|
|
* Determines whether a backend user is allowed to access the backend. |
|
2345
|
|
|
* |
|
2346
|
|
|
* The conditions are: |
|
2347
|
|
|
* + backend user is a regular user and adminOnly is not defined |
|
2348
|
|
|
* + backend user is an admin user |
|
2349
|
|
|
* + backend user is used in CLI context and adminOnly is explicitly set to "2" (see CommandLineUserAuthentication) |
|
2350
|
|
|
* + backend user is being controlled by an admin user |
|
2351
|
|
|
* |
|
2352
|
|
|
* @return bool Whether a backend user is allowed to access the backend |
|
2353
|
|
|
* @internal |
|
2354
|
|
|
*/ |
|
2355
|
|
|
public function isUserAllowedToLogin() |
|
2356
|
|
|
{ |
|
2357
|
|
|
$isUserAllowedToLogin = false; |
|
2358
|
|
|
$adminOnlyMode = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['adminOnly']; |
|
2359
|
|
|
// Backend user is allowed if adminOnly is not set or user is an admin: |
|
2360
|
|
|
if (!$adminOnlyMode || $this->isAdmin()) { |
|
2361
|
|
|
$isUserAllowedToLogin = true; |
|
2362
|
|
|
} elseif ($backUserId = $this->getOriginalUserIdWhenInSwitchUserMode()) { |
|
2363
|
|
|
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users'); |
|
2364
|
|
|
$isUserAllowedToLogin = (bool)$queryBuilder->count('uid') |
|
2365
|
|
|
->from('be_users') |
|
2366
|
|
|
->where( |
|
2367
|
|
|
$queryBuilder->expr()->eq( |
|
2368
|
|
|
'uid', |
|
2369
|
|
|
$queryBuilder->createNamedParameter($backUserId, \PDO::PARAM_INT) |
|
2370
|
|
|
), |
|
2371
|
|
|
$queryBuilder->expr()->eq('admin', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)) |
|
2372
|
|
|
) |
|
2373
|
|
|
->execute() |
|
2374
|
|
|
->fetchOne(); |
|
2375
|
|
|
} |
|
2376
|
|
|
return $isUserAllowedToLogin; |
|
2377
|
|
|
} |
|
2378
|
|
|
|
|
2379
|
|
|
/** |
|
2380
|
|
|
* Logs out the current user and clears the form protection tokens. |
|
2381
|
|
|
*/ |
|
2382
|
|
|
public function logoff() |
|
2383
|
|
|
{ |
|
2384
|
|
|
if (isset($GLOBALS['BE_USER']) |
|
2385
|
|
|
&& $GLOBALS['BE_USER'] instanceof self |
|
2386
|
|
|
&& isset($GLOBALS['BE_USER']->user['uid']) |
|
2387
|
|
|
) { |
|
2388
|
|
|
FormProtectionFactory::get()->clean(); |
|
2389
|
|
|
// Release the locked records |
|
2390
|
|
|
$this->releaseLockedRecords((int)$GLOBALS['BE_USER']->user['uid']); |
|
2391
|
|
|
|
|
2392
|
|
|
if ($this->isSystemMaintainer()) { |
|
2393
|
|
|
// If user is system maintainer, destroy its possibly valid install tool session. |
|
2394
|
|
|
$session = new SessionService(); |
|
2395
|
|
|
$session->destroySession(); |
|
2396
|
|
|
} |
|
2397
|
|
|
} |
|
2398
|
|
|
parent::logoff(); |
|
2399
|
|
|
} |
|
2400
|
|
|
|
|
2401
|
|
|
/** |
|
2402
|
|
|
* Remove any "locked records" added for editing for the given user (= current backend user) |
|
2403
|
|
|
* @param int $userId |
|
2404
|
|
|
*/ |
|
2405
|
|
|
protected function releaseLockedRecords(int $userId) |
|
2406
|
|
|
{ |
|
2407
|
|
|
if ($userId > 0) { |
|
2408
|
|
|
GeneralUtility::makeInstance(ConnectionPool::class) |
|
2409
|
|
|
->getConnectionForTable('sys_lockedrecords') |
|
2410
|
|
|
->delete( |
|
2411
|
|
|
'sys_lockedrecords', |
|
2412
|
|
|
['userid' => $userId] |
|
2413
|
|
|
); |
|
2414
|
|
|
} |
|
2415
|
|
|
} |
|
2416
|
|
|
|
|
2417
|
|
|
/** |
|
2418
|
|
|
* Returns the uid of the backend user to return to. |
|
2419
|
|
|
* This is set when the current session is a "switch-user" session. |
|
2420
|
|
|
* |
|
2421
|
|
|
* @return int|null The user id |
|
2422
|
|
|
* @internal should only be used from within TYPO3 Core |
|
2423
|
|
|
*/ |
|
2424
|
|
|
public function getOriginalUserIdWhenInSwitchUserMode(): ?int |
|
2425
|
|
|
{ |
|
2426
|
|
|
$originalUserId = $this->getSessionData('backuserid'); |
|
2427
|
|
|
return $originalUserId ? (int)$originalUserId : null; |
|
2428
|
|
|
} |
|
2429
|
|
|
|
|
2430
|
|
|
/** |
|
2431
|
|
|
* @internal |
|
2432
|
|
|
*/ |
|
2433
|
|
|
protected function evaluateMfaRequirements(): void |
|
2434
|
|
|
{ |
|
2435
|
|
|
// In case the current session is a "switch-user" session, MFA is not required |
|
2436
|
|
|
if ($this->getOriginalUserIdWhenInSwitchUserMode() !== null) { |
|
2437
|
|
|
$this->logger->debug('MFA is skipped in switch user mode', [ |
|
|
|
|
|
|
2438
|
|
|
$this->userid_column => $this->user[$this->userid_column], |
|
2439
|
|
|
$this->username_column => $this->user[$this->username_column], |
|
2440
|
|
|
]); |
|
2441
|
|
|
return; |
|
2442
|
|
|
} |
|
2443
|
|
|
parent::evaluateMfaRequirements(); |
|
2444
|
|
|
} |
|
2445
|
|
|
} |
|
2446
|
|
|
|
In PHP, under loose comparison (like
==, or!=, orswitchconditions), values of different types might be equal.For
integervalues, zero is a special case, in particular the following results might be unexpected: