1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\FrontendLogin\Controller;
19
20use Psr\EventDispatcher\EventDispatcherInterface;
21use Psr\Http\Message\ResponseInterface;
22use TYPO3\CMS\Core\Authentication\LoginType;
23use TYPO3\CMS\Core\Context\Context;
24use TYPO3\CMS\Core\Context\UserAspect;
25use TYPO3\CMS\Core\Utility\GeneralUtility;
26use TYPO3\CMS\Extbase\Http\ForwardResponse;
27use TYPO3\CMS\FrontendLogin\Configuration\RedirectConfiguration;
28use TYPO3\CMS\FrontendLogin\Event\BeforeRedirectEvent;
29use TYPO3\CMS\FrontendLogin\Event\LoginConfirmedEvent;
30use TYPO3\CMS\FrontendLogin\Event\LoginErrorOccurredEvent;
31use TYPO3\CMS\FrontendLogin\Event\LogoutConfirmedEvent;
32use TYPO3\CMS\FrontendLogin\Event\ModifyLoginFormViewEvent;
33use TYPO3\CMS\FrontendLogin\Redirect\RedirectHandler;
34use TYPO3\CMS\FrontendLogin\Redirect\ServerRequestHandler;
35use TYPO3\CMS\FrontendLogin\Service\UserService;
36
37/**
38 * Used for plugin login
39 */
40class LoginController extends AbstractLoginFormController
41{
42    /**
43     * @var string
44     */
45    public const MESSAGEKEY_DEFAULT = 'welcome';
46
47    /**
48     * @var string
49     */
50    public const MESSAGEKEY_ERROR = 'error';
51
52    /**
53     * @var string
54     */
55    public const MESSAGEKEY_LOGOUT = 'logout';
56
57    /**
58     * @var RedirectHandler
59     */
60    protected $redirectHandler;
61
62    /**
63     * @var string
64     */
65    protected $loginType = '';
66
67    /**
68     * @var string
69     */
70    protected $redirectUrl = '';
71
72    /**
73     * @var ServerRequestHandler
74     */
75    protected $requestHandler;
76
77    /**
78     * @var UserService
79     */
80    protected $userService;
81
82    /**
83     * @var RedirectConfiguration
84     */
85    protected $configuration;
86
87    /**
88     * @var EventDispatcherInterface
89     */
90    protected $eventDispatcher;
91
92    /**
93     * @var UserAspect
94     */
95    protected $userAspect;
96
97    /**
98     * @var bool
99     */
100    protected $showCookieWarning = false;
101
102    public function __construct(
103        RedirectHandler $redirectHandler,
104        ServerRequestHandler $requestHandler,
105        UserService $userService,
106        EventDispatcherInterface $eventDispatcher
107    ) {
108        $this->redirectHandler = $redirectHandler;
109        $this->requestHandler = $requestHandler;
110        $this->userService = $userService;
111        $this->eventDispatcher = $eventDispatcher;
112        $this->userAspect = GeneralUtility::makeInstance(Context::class)->getAspect('frontend.user');
113    }
114
115    /**
116     * Initialize redirects
117     */
118    public function initializeAction(): void
119    {
120        $this->loginType = (string)$this->requestHandler->getPropertyFromGetAndPost('logintype');
121        $this->configuration = RedirectConfiguration::fromSettings($this->settings);
122
123        if ($this->isLoginOrLogoutInProgress() && !$this->isRedirectDisabled()) {
124            if ($this->userAspect->isLoggedIn() && $this->userService->cookieWarningRequired()) {
125                $this->showCookieWarning = true;
126                return;
127            }
128
129            $this->redirectUrl = $this->redirectHandler->processRedirect(
130                $this->loginType,
131                $this->configuration,
132                $this->request->hasArgument('redirectReferrer') ? $this->request->getArgument('redirectReferrer') : ''
133            );
134        }
135    }
136
137    /**
138     * Show login form
139     */
140    public function loginAction(): ResponseInterface
141    {
142        if ($this->isLogoutSuccessful()) {
143            $this->eventDispatcher->dispatch(new LogoutConfirmedEvent($this, $this->view));
144        } elseif ($this->hasLoginErrorOccurred()) {
145            $this->eventDispatcher->dispatch(new LoginErrorOccurredEvent());
146        }
147
148        if (($forwardResponse = $this->handleLoginForwards()) !== null) {
149            return $forwardResponse;
150        }
151        $this->handleRedirect();
152
153        $this->eventDispatcher->dispatch(new ModifyLoginFormViewEvent($this->view));
154
155        $this->view->assignMultiple(
156            [
157                'cookieWarning' => $this->showCookieWarning,
158                'messageKey' => $this->getStatusMessageKey(),
159                'storagePid' => implode(',', $this->getStorageFolders()),
160                'permaloginStatus' => $this->getPermaloginStatus(),
161                'redirectURL' => $this->redirectHandler->getLoginFormRedirectUrl($this->configuration, $this->isRedirectDisabled()),
162                'redirectReferrer' => $this->request->hasArgument('redirectReferrer') ? (string)$this->request->getArgument('redirectReferrer'): '',
163                'referer' => $this->requestHandler->getPropertyFromGetAndPost('referer'),
164                'noRedirect' => $this->isRedirectDisabled(),
165            ]
166        );
167
168        return $this->htmlResponse();
169    }
170
171    /**
172     * User overview for logged in users
173     *
174     * @param bool $showLoginMessage
175     * @return ResponseInterface
176     */
177    public function overviewAction(bool $showLoginMessage = false): ResponseInterface
178    {
179        if (!$this->userAspect->isLoggedIn()) {
180            return new ForwardResponse('login');
181        }
182
183        $this->eventDispatcher->dispatch(new LoginConfirmedEvent($this, $this->view));
184        $this->handleRedirect();
185
186        $this->view->assignMultiple(
187            [
188                'cookieWarning' => $this->showCookieWarning,
189                'user' => $this->userService->getFeUserData(),
190                'showLoginMessage' => $showLoginMessage,
191            ]
192        );
193
194        return $this->htmlResponse();
195    }
196
197    /**
198     * Show logout form
199     * @param int $redirectPageLogout
200     * @return ResponseInterface
201     */
202    public function logoutAction(int $redirectPageLogout = 0): ResponseInterface
203    {
204        $this->handleRedirect();
205
206        $this->view->assignMultiple(
207            [
208                'cookieWarning' => $this->showCookieWarning,
209                'user' => $this->userService->getFeUserData(),
210                'storagePid' => implode(',', $this->getStorageFolders()),
211                'noRedirect' => $this->isRedirectDisabled(),
212                'actionUri' => $this->redirectHandler->getLogoutFormRedirectUrl($this->configuration, $redirectPageLogout, $this->isRedirectDisabled()),
213            ]
214        );
215
216        return $this->htmlResponse();
217    }
218
219    /**
220     * Handles the redirect when $this->redirectUrl is not empty
221     */
222    protected function handleRedirect(): void
223    {
224        if ($this->redirectUrl !== '') {
225            $this->eventDispatcher->dispatch(new BeforeRedirectEvent($this->loginType, $this->redirectUrl));
226            $this->redirectToUri($this->redirectUrl);
227        }
228    }
229
230    /**
231     * Handle forwards to overview and logout actions from login action
232     */
233    protected function handleLoginForwards(): ?ResponseInterface
234    {
235        if ($this->shouldRedirectToOverview()) {
236            return (new ForwardResponse('overview'))->withArguments(['showLoginMessage' => true]);
237        }
238
239        if ($this->userAspect->isLoggedIn()) {
240            return (new ForwardResponse('logout'))->withArguments(['redirectPageLogout' => $this->settings['redirectPageLogout']]);
241        }
242
243        return null;
244    }
245
246    /**
247     * The permanent login checkbox should only be shown if permalogin is not deactivated (-1),
248     * not forced to be always active (2) and lifetime is greater than 0
249     *
250     * @return int
251     */
252    protected function getPermaloginStatus(): int
253    {
254        $permaLogin = (int)$GLOBALS['TYPO3_CONF_VARS']['FE']['permalogin'];
255
256        return $this->isPermaloginDisabled($permaLogin) ? -1 : $permaLogin;
257    }
258
259    protected function isPermaloginDisabled(int $permaLogin): bool
260    {
261        return $permaLogin > 1
262               || (int)($this->settings['showPermaLogin'] ?? 0) === 0
263               || $GLOBALS['TYPO3_CONF_VARS']['FE']['lifetime'] === 0;
264    }
265
266    /**
267     * Redirect to overview on login successful and setting showLogoutFormAfterLogin disabled
268     *
269     * @return bool
270     */
271    protected function shouldRedirectToOverview(): bool
272    {
273        return $this->userAspect->isLoggedIn()
274               && ($this->loginType === LoginType::LOGIN)
275               && !($this->settings['showLogoutFormAfterLogin'] ?? 0);
276    }
277
278    /**
279     * Return message key based on user login status
280     *
281     * @return string
282     */
283    protected function getStatusMessageKey(): string
284    {
285        $messageKey = self::MESSAGEKEY_DEFAULT;
286        if ($this->hasLoginErrorOccurred()) {
287            $messageKey = self::MESSAGEKEY_ERROR;
288        } elseif ($this->loginType === LoginType::LOGOUT) {
289            $messageKey = self::MESSAGEKEY_LOGOUT;
290        }
291
292        return $messageKey;
293    }
294
295    protected function isLoginOrLogoutInProgress(): bool
296    {
297        return $this->loginType === LoginType::LOGIN || $this->loginType === LoginType::LOGOUT;
298    }
299
300    /**
301     * Is redirect disabled by setting or noredirect parameter
302     *
303     * @return bool
304     */
305    public function isRedirectDisabled(): bool
306    {
307        return
308            $this->request->hasArgument('noredirect')
309            || ($this->settings['noredirect'] ?? false)
310            || ($this->settings['redirectDisable'] ?? false);
311    }
312
313    protected function isLogoutSuccessful(): bool
314    {
315        return $this->loginType === LoginType::LOGOUT && !$this->userAspect->isLoggedIn();
316    }
317
318    protected function hasLoginErrorOccurred(): bool
319    {
320        return $this->loginType === LoginType::LOGIN && !$this->userAspect->isLoggedIn();
321    }
322}
323