• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / typo3-warming / 14431906128

13 Apr 2025 05:54PM UTC coverage: 91.301% (-0.2%) from 91.486%
14431906128

Pull #839

github

eliashaeussler
[!!!][TASK] Convert `AccessUtility` to `WarmupPermissionGuard`
Pull Request #839: [!!!][TASK] Convert `AccessUtility` to `WarmupPermissionGuard`

55 of 60 new or added lines in 4 files covered. (91.67%)

5 existing lines in 2 files now uncovered.

1123 of 1230 relevant lines covered (91.3%)

8.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.1
/Classes/Security/WarmupPermissionGuard.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "warming".
7
 *
8
 * Copyright (C) 2021-2025 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\Typo3Warming\Security;
25

26
use EliasHaeussler\Typo3Warming\Utility;
27
use Symfony\Component\DependencyInjection;
28
use TYPO3\CMS\Backend;
29
use TYPO3\CMS\Core;
30

31
/**
32
 * WarmupPermissionGuard
33
 *
34
 * @author Elias Häußler <elias@haeussler.dev>
35
 * @license GPL-2.0-or-later
36
 */
37
final class WarmupPermissionGuard
38
{
39
    public function __construct(
34✔
40
        #[DependencyInjection\Attribute\Autowire('@cache.runtime')]
41
        private readonly Core\Cache\Frontend\FrontendInterface $cache,
42
    ) {}
34✔
43

44
    public function canWarmupCacheOfPage(
17✔
45
        int $pageId,
46
        ?int $languageId = null,
47
        Core\Authentication\BackendUserAuthentication|null|false $backendUser = null,
48
    ): bool {
49
        $backendUser = $this->resolveHandledBackendUser($backendUser);
17✔
50

51
        return $this->getFromCache(
17✔
52
            ['canWarmupCacheOfPage', $pageId, $languageId, $backendUser],
17✔
53
            function () use ($pageId, $languageId, $backendUser) {
17✔
54
                $pageAccess = $this->hasPageAccess($pageId, $languageId, $backendUser);
17✔
55

56
                if ($backendUser === null) {
17✔
NEW
57
                    return $pageAccess;
×
58
                }
59

60
                return $pageAccess
17✔
61
                    && $this->isAllowedPage($pageId, $backendUser)
17✔
62
                    && ($languageId === null || $this->hasLanguageAccess($languageId, $backendUser))
17✔
63
                ;
17✔
64
            },
17✔
65
        );
17✔
66
    }
67

68
    public function canWarmupCacheOfSite(
26✔
69
        Core\Site\Entity\Site $site,
70
        ?int $languageId = null,
71
        Core\Authentication\BackendUserAuthentication|null|false $backendUser = null,
72
    ): bool {
73
        $backendUser = $this->resolveHandledBackendUser($backendUser);
26✔
74

75
        return $this->getFromCache(
26✔
76
            ['canWarmupCacheOfSite', $site, $languageId, $backendUser],
26✔
77
            function () use ($site, $languageId, $backendUser) {
26✔
78
                $pageAccess = $this->hasPageAccess($site->getRootPageId(), $languageId, $backendUser);
26✔
79

80
                if ($backendUser === null) {
26✔
NEW
81
                    return $pageAccess;
×
82
                }
83

84
                return $pageAccess
26✔
85
                    && $this->isAllowedSite($site->getIdentifier(), $backendUser)
26✔
86
                    && ($languageId === null || $this->hasLanguageAccess($languageId, $backendUser))
26✔
87
                ;
26✔
88
            },
26✔
89
        );
26✔
90
    }
91

92
    private function hasPageAccess(
33✔
93
        int $pageId,
94
        ?int $languageId = null,
95
        ?Core\Authentication\BackendUserAuthentication $backendUser = null,
96
    ): bool {
97
        // Fetch record and record localization (if language is given and is not default language),
98
        // additionally check for available pages by adding hidden=0 as additional WHERE clause
99
        $record = Backend\Utility\BackendUtility::getRecord('pages', $pageId, '*', 'hidden = 0');
33✔
100
        if ($languageId !== null && $languageId > 0) {
33✔
101
            $record = Backend\Utility\BackendUtility::getRecordLocalization('pages', $pageId, $languageId, 'hidden = 0');
9✔
102
        }
103

104
        // Early return if record is inaccessible
105
        if (!\is_array($record) || $record === []) {
33✔
106
            return false;
2✔
107
        }
108

109
        // Select first record inside list of records which is potentially returned by
110
        // BackendUtility::getRecordLocalization()
111
        if (array_is_list($record)) {
31✔
112
            $record = reset($record);
7✔
113
        }
114

115
        // Early return if localized record is inaccessible
116
        // (this should never happen, but makes PHPStan happy)
117
        if (!\is_array($record) || $record === []) {
31✔
118
            return false;
×
119
        }
120

121
        // Early return if no backend user rights should be checked
122
        if ($backendUser === null) {
31✔
NEW
123
            return true;
×
124
        }
125

126
        // Early return if backend user has admin privileges
127
        if ($backendUser->isAdmin()) {
31✔
128
            return true;
17✔
129
        }
130

131
        return $backendUser->doesUserHaveAccess($record, Core\Type\Bitmask\Permission::PAGE_SHOW);
14✔
132
    }
133

134
    private function hasLanguageAccess(int $languageId, Core\Authentication\BackendUserAuthentication $backendUser): bool
9✔
135
    {
136
        if ($backendUser->isAdmin()) {
9✔
137
            return true;
6✔
138
        }
139

140
        return $backendUser->checkLanguageAccess($languageId);
3✔
141
    }
142

143
    private function isAllowedPage(int $pageId, Core\Authentication\BackendUserAuthentication $backendUser): bool
16✔
144
    {
145
        if ($backendUser->isAdmin()) {
16✔
146
            return true;
7✔
147
        }
148

149
        $userTsConfig = $backendUser->getTSConfig();
9✔
150
        $allowedPages = Core\Utility\GeneralUtility::trimExplode(',', (string)($userTsConfig['options.']['cacheWarmup.']['allowedPages'] ?? ''), true);
9✔
151

152
        // Early return if no allowed pages are configured
153
        if ($allowedPages === []) {
9✔
154
            return false;
2✔
155
        }
156

157
        // Fetch rootline of current page id
158
        $rootline = Core\Utility\GeneralUtility::makeInstance(Core\Utility\RootlineUtility::class, $pageId)->get();
7✔
159
        $rootlineIds = array_column($rootline, 'uid');
7✔
160

161
        foreach ($allowedPages as $allowedPage) {
7✔
162
            $recursiveLookup = str_ends_with($allowedPage, '+');
7✔
163
            $normalizedPageId = rtrim($allowedPage, '+');
7✔
164

165
            // Continue if configured page is not numeric
166
            if (!is_numeric($normalizedPageId)) {
7✔
167
                continue;
168
            }
169

170
            // Check if configured page id matches current page id
171
            if ((int)$normalizedPageId === $pageId) {
7✔
172
                return true;
5✔
173
            }
174

175
            // Check if current page is in rootline of configured page id
176
            if ($recursiveLookup && \in_array((int)$normalizedPageId, $rootlineIds, true)) {
2✔
177
                return true;
1✔
178
            }
179
        }
180

181
        return false;
1✔
182
    }
183

184
    private function isAllowedSite(string $siteIdentifier, Core\Authentication\BackendUserAuthentication $backendUser): bool
25✔
185
    {
186
        if ($backendUser->isAdmin()) {
25✔
187
            return true;
16✔
188
        }
189

190
        $userTsConfig = $backendUser->getTSConfig();
9✔
191
        $allowedSites = (string)($userTsConfig['options.']['cacheWarmup.']['allowedSites'] ?? '');
9✔
192

193
        return Core\Utility\GeneralUtility::inList($allowedSites, $siteIdentifier);
9✔
194
    }
195

196
    /**
197
     * @param list<mixed> $identifiers
198
     * @param \Closure(): bool $fn
199
     */
200
    private function getFromCache(array $identifiers, \Closure $fn): bool
33✔
201
    {
202
        $identifier = 'warming_warmupPermissionGuard_' . sha1(serialize($identifiers));
33✔
203

204
        if (!$this->cache->has($identifier)) {
33✔
205
            $this->cache->set($identifier, $fn());
33✔
206
        }
207

208
        $result = $this->cache->get($identifier);
33✔
209

210
        if (!is_bool($result)) {
33✔
NEW
211
            return false;
×
212
        }
213

214
        return $result;
33✔
215
    }
216

217
    private function resolveHandledBackendUser(
33✔
218
        Core\Authentication\BackendUserAuthentication|null|false $backendUser,
219
    ): ?Core\Authentication\BackendUserAuthentication {
220
        return match ($backendUser) {
33✔
221
            null => Utility\BackendUtility::getBackendUser(),
33✔
NEW
222
            false => null,
×
223
            default => $backendUser,
33✔
224
        };
33✔
225
    }
226
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc