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