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