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