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