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