Completed
Pull Request — master (#4212)
by Craig
05:34
created

AccessController::logoutAction()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 16
nc 8
nop 6
dl 0
loc 29
rs 9.1111
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula Foundation - https://ziku.la/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Zikula\UsersModule\Controller;
15
16
use LogicException;
17
use Symfony\Component\HttpFoundation\RedirectResponse;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\Response;
20
use Symfony\Component\Routing\Annotation\Route;
21
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
22
use Zikula\Bundle\CoreBundle\Controller\AbstractController;
23
use Zikula\Bundle\CoreBundle\Event\GenericEvent;
24
use Zikula\Bundle\HookBundle\Dispatcher\HookDispatcherInterface;
25
use Zikula\Bundle\HookBundle\Hook\ProcessHook;
26
use Zikula\Bundle\HookBundle\Hook\ValidationHook;
27
use Zikula\UsersModule\AccessEvents;
28
use Zikula\UsersModule\Api\ApiInterface\CurrentUserApiInterface;
29
use Zikula\UsersModule\AuthenticationMethodInterface\NonReEntrantAuthenticationMethodInterface;
30
use Zikula\UsersModule\AuthenticationMethodInterface\ReEntrantAuthenticationMethodInterface;
31
use Zikula\UsersModule\Collector\AuthenticationMethodCollector;
32
use Zikula\UsersModule\Constant;
33
use Zikula\UsersModule\Entity\RepositoryInterface\UserRepositoryInterface;
34
use Zikula\UsersModule\Entity\UserEntity;
35
use Zikula\UsersModule\Event\LoginFormPostCreatedEvent;
36
use Zikula\UsersModule\Event\LoginFormPostValidatedEvent;
37
use Zikula\UsersModule\Event\UserPostLoginFailureEvent;
38
use Zikula\UsersModule\Event\UserPostLoginSuccessEvent;
39
use Zikula\UsersModule\Event\UserPreLoginSuccessEvent;
40
use Zikula\UsersModule\Exception\InvalidAuthenticationMethodLoginFormException;
41
use Zikula\UsersModule\Form\Type\DefaultLoginType;
42
use Zikula\UsersModule\Helper\AccessHelper;
43
use Zikula\UsersModule\HookSubscriber\LoginUiHooksSubscriber;
44
45
class AccessController extends AbstractController
46
{
47
    /**
48
     * @Route("/login", options={"zkNoBundlePrefix"=1})
49
     *
50
     * @throws InvalidAuthenticationMethodLoginFormException
51
     */
52
    public function loginAction(
53
        Request $request,
54
        CurrentUserApiInterface $currentUserApi,
55
        UserRepositoryInterface $userRepository,
56
        AuthenticationMethodCollector $authenticationMethodCollector,
57
        AccessHelper $accessHelper,
58
        EventDispatcherInterface $eventDispatcher,
59
        HookDispatcherInterface $hookDispatcher
60
    ): Response {
61
        if ($currentUserApi->isLoggedIn()) {
62
            return $this->redirectToRoute('zikulausersmodule_account_menu');
63
        }
64
        $returnUrl = $request->query->get('returnUrl', '');
65
66
        $session = $request->hasSession() ? $request->getSession() : null;
67
68
        $selectedMethod = null !== $session ? $session->get('authenticationMethod') : '';
69
        $selectedMethod = $request->query->get('authenticationMethod', $selectedMethod);
70
        if (empty($selectedMethod) && count($authenticationMethodCollector->getActiveKeys()) > 1) {
71
            // there are multiple authentication methods available and none selected yet, so let the user choose one
72
            return $this->render('@ZikulaUsersModule/Access/authenticationMethodSelector.html.twig', [
73
                'collector' => $authenticationMethodCollector,
74
                'path' => 'zikulausersmodule_access_login'
75
            ]);
76
        }
77
        if (empty($selectedMethod) && 1 === count($authenticationMethodCollector->getActiveKeys())) {
78
            // there is only one authentication method available, so use this
79
            $selectedMethod = $authenticationMethodCollector->getActiveKeys()[0];
80
        }
81
        if (null !== $session) {
82
            // save method to session for reEntrant needs
83
            $session->set('authenticationMethod', $selectedMethod);
84
            if (!empty($returnUrl)) {
85
                // save returnUrl to session for reEntrant needs
86
                $session->set('returnUrl', $returnUrl);
87
            }
88
        }
89
90
        $authenticationMethod = $authenticationMethodCollector->get($selectedMethod);
91
        $rememberMe = false;
92
93
        $loginHeader = $this->renderView('@ZikulaUsersModule/Access/loginHeader.html.twig');
94
        $loginFooter = $this->renderView('@ZikulaUsersModule/Access/loginFooter.html.twig');
95
96
        $form = null;
97
        if ($authenticationMethod instanceof NonReEntrantAuthenticationMethodInterface) {
98
            $form = $this->createForm($authenticationMethod->getLoginFormClassName());
99
            if (!$form->has('rememberme')) {
100
                throw new InvalidAuthenticationMethodLoginFormException();
101
            }
102
            $loginFormEvent = new LoginFormPostCreatedEvent($form);
103
            $eventDispatcher->dispatch($loginFormEvent);
104
            $form->handleRequest($request);
105
            if ($form->isSubmitted() && $form->isValid()) {
106
                $data = $form->getData();
107
                $rememberMe = $data['rememberme'];
108
                $uid = $authenticationMethod->authenticate($data);
109
            } else {
110
                return $this->render($authenticationMethod->getLoginTemplateName(), [
111
                    'loginHeader' => $loginHeader,
112
                    'loginFooter' => $loginFooter,
113
                    'form' => $form->createView(),
114
                    'additionalTemplates' => isset($loginFormEvent) ? $loginFormEvent->getTemplates() : []
115
                ]);
116
            }
117
        } elseif ($authenticationMethod instanceof ReEntrantAuthenticationMethodInterface) {
118
            // provide temp value for uid until form gives real value.
119
            $uid = 'POST' === $request->getMethod() ? Constant::USER_ID_ANONYMOUS : $authenticationMethod->authenticate();
120
            $hasListeners = $eventDispatcher->hasListeners(LoginFormPostCreatedEvent::class);
121
            $hookBindings = $hookDispatcher->getBindingsFor('subscriber.users.ui_hooks.login_screen');
122
            if ($hasListeners || count($hookBindings) > 0) {
123
                $form = $this->createForm(DefaultLoginType::class, ['uid' => $uid]);
124
                $loginFormEvent = new LoginFormPostCreatedEvent($form);
125
                $eventDispatcher->dispatch($loginFormEvent);
126
                if ($form->count() > 3) { // count > 3 means that the LoginFormPostCreatedEvent event added some form children
127
                    $form->handleRequest($request);
128
                    if ($form->isSubmitted() && $form->isValid()) {
129
                        $uid = $form->get('uid')->getData();
130
                        $rememberMe = $form->get('rememberme')->getData();
131
                    } else {
132
                        return $this->render('@ZikulaUsersModule/Access/defaultLogin.html.twig', [
133
                            'loginHeader' => $loginHeader,
134
                            'loginFooter' => $loginFooter,
135
                            'form' => $form->createView(),
136
                            'additionalTemplates' => isset($loginFormEvent) ? $loginFormEvent->getTemplates() : []
137
                        ]);
138
                    }
139
                }
140
            }
141
        } else {
142
            throw new LogicException($this->trans('Invalid authentication method.'));
143
        }
144
        $user = null;
145
        if (isset($uid)) {
146
            /** @var UserEntity $user */
147
            $user = $userRepository->find($uid);
148
            if (isset($user)) {
149
                $hook = new ValidationHook();
150
                $hookDispatcher->dispatch(LoginUiHooksSubscriber::LOGIN_VALIDATE, $hook);
151
                $validators = $hook->getValidators();
152
                if (!$validators->hasErrors() && $accessHelper->loginAllowed($user)) {
153
                    if (isset($form)) {
154
                        $formDataEvent = new LoginFormPostValidatedEvent($form, $user);
155
                        $eventDispatcher->dispatch($formDataEvent);
156
                    }
157
                    $hookDispatcher->dispatch(LoginUiHooksSubscriber::LOGIN_PROCESS, new ProcessHook($user));
158
                    $userPreSuccessLoginEvent = new UserPreLoginSuccessEvent($user, $selectedMethod);
159
                    $eventDispatcher->dispatch($userPreSuccessLoginEvent);
160
                    if (!$userPreSuccessLoginEvent->isPropagationStopped()) {
161
                        $returnUrlFromSession = null !== $session ? $session->get('returnUrl', $returnUrl) : $returnUrl;
162
                        $returnUrlFromSession = urldecode($returnUrlFromSession);
163
                        $accessHelper->login($user, $rememberMe);
164
                        $userPostSuccessLoginEvent = new UserPostLoginSuccessEvent($user, $selectedMethod);
165
                        $userPostSuccessLoginEvent->setRedirectUrl($returnUrlFromSession);
166
                        $eventDispatcher->dispatch($userPostSuccessLoginEvent);
167
                        $returnUrl = $userPostSuccessLoginEvent->getRedirectUrl();
168
                    } else {
169
                        if ($userPreSuccessLoginEvent->hasFlashes()) {
170
                            $this->addFlash('danger', $userPreSuccessLoginEvent->getFlashesAsString());
171
                        }
172
                        $returnUrl = $userPreSuccessLoginEvent->getRedirectUrl();
173
                    }
174
175
                    return !empty($returnUrl) ? $this->redirect($returnUrl) : $this->redirectToRoute('home');
176
                }
177
            }
178
        }
179
        // login failed
180
        $this->addFlash('error', 'Login failed.');
181
        if (null !== $session) {
182
            $session->remove('authenticationMethod');
183
        }
184
        $userPostFailLoginEvent = new UserPostLoginFailureEvent($user, $authenticationMethod->getAlias());
185
        $userPostFailLoginEvent->setRedirectUrl($returnUrl);
186
        $returnUrl = $userPostFailLoginEvent->getRedirectUrl();
187
188
        return !empty($returnUrl) ? $this->redirect($returnUrl) : $this->redirectToRoute('home');
189
    }
190
191
    /**
192
     * @Route("/logout/{returnUrl}", options={"zkNoBundlePrefix"=1})
193
     */
194
    public function logoutAction(
195
        Request $request,
196
        CurrentUserApiInterface $currentUserApi,
197
        UserRepositoryInterface $userRepository,
198
        AccessHelper $accessHelper,
199
        EventDispatcherInterface $eventDispatcher,
200
        string $returnUrl = null
201
    ): RedirectResponse {
202
        if ($currentUserApi->isLoggedIn()) {
203
            $uid = $currentUserApi->get('uid');
204
            $user = $userRepository->find($uid);
205
            if ($accessHelper->logout()) {
206
                $authMethod = null;
207
                if ($request->hasSession() && ($session = $request->getSession())) {
208
                    $authMethod = $session->get('authenticationMethod');
209
                }
210
                $event = new GenericEvent($user, [
211
                    'authenticationMethod' => $authMethod,
212
                    'uid' => $uid,
213
                ]);
214
                $eventDispatcher->dispatch($event, AccessEvents::LOGOUT_SUCCESS);
215
            } else {
216
                $this->addFlash('error', 'Error! You have not been logged out.');
217
            }
218
        }
219
220
        return isset($returnUrl)
221
            ? $this->redirect($returnUrl)
222
            : $this->redirectToRoute('home', ['_locale' => $this->getParameter('locale')])
223
        ;
224
    }
225
}
226