Passed
Push — master ( f3fb78...a8f440 )
by Marcel
07:48
created

UserController::restore()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 11
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 16
ccs 0
cts 8
cp 0
crap 30
rs 9.6111
1
<?php
2
3
namespace App\Controller;
4
5
use AdAuth\AdAuthInterface;
6
use AdAuth\Credentials;
7
use AdAuth\Response\PasswordFailedResponse;
8
use AdAuth\Response\PasswordSuccessResponse;
9
use AdAuth\SocketException;
10
use App\Entity\ActiveDirectoryUser;
11
use App\Entity\User;
12
use App\Form\AttributeDataTrait;
13
use App\Form\ResetPasswordType;
14
use App\Form\ResetPasswortActiveDirectoryType;
15
use App\Form\UserType;
16
use App\Entity\UserType as UserTypeEntity;
17
use App\Repository\UserRepositoryInterface;
18
use App\Repository\UserTypeRepositoryInterface;
19
use App\Saml\AttributeValueProvider;
20
use App\Security\ForgotPassword\ForgotPasswordManager;
21
use App\Security\Session\LogoutHelper;
22
use App\Service\AttributePersister;
23
use App\Service\AttributeResolver;
24
use App\Utils\ArrayUtils;
25
use App\View\Filter\UserRoleFilter;
26
use App\View\Filter\UserTypeFilter;
27
use SchulIT\CommonBundle\Form\ConfirmType;
28
use SchulIT\CommonBundle\Utils\RefererHelper;
29
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
30
use Symfony\Component\HttpFoundation\Request;
31
use Symfony\Component\HttpFoundation\Response;
32
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
33
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
34
use Symfony\Component\Routing\Annotation\Route;
35
use Symfony\Contracts\Translation\TranslatorInterface;
36
37
class UserController extends AbstractController {
38
39
    use AttributeDataTrait;
40
41
    public const USERS_PER_PAGE = 25;
42
43
    public const CsrfTokenId = 'remove_user';
44
    public const CsrfTokenKey = '_csrf_token';
45
46
    public function __construct(private readonly UserRepositoryInterface $repository)
47
    {
48
    }
49
50
    private function internalDisplay(Request $request, UserTypeFilter $typeFilter, UserRoleFilter $roleFilter, bool $deleted): Response {
51
        $q = $request->query->get('q', null);
52
        $page = $request->query->getInt('page', 1);
53
        $grade = $request->query->get('grade', null);
54
55
        $grades = $this->repository->findGrades();
56
57
        if(!in_array($grade, $grades)) {
58
            $grade = null;
59
        }
60
61
        $typeFilterView = $typeFilter->handle($request->query->get('type'));
62
        $roleFilterView = $roleFilter->handle($request->query->get('role'));
63
64
        $withoutParents = $typeFilterView->getCurrentType() !== null && $typeFilterView->getCurrentType()->getAlias() === 'student' && $request->query->get('without_parents') === '✓';
65
66
        $paginator = $this->repository->getPaginatedUsers(static::USERS_PER_PAGE, $page, $typeFilterView->getCurrentType(), $roleFilterView->getCurrentRole(), $q, $grade, $deleted, $withoutParents);
67
68
        $pages = 1;
69
70
        if($paginator->count() > 0) {
71
            $pages = ceil((float)$paginator->count() / static::USERS_PER_PAGE);
72
        }
73
74
        $template = $deleted ? 'users/trash.html.twig' : 'users/index.html.twig';
75
76
        $statistics = [ ];
77
78
        if($deleted === false) {
79
            foreach ($typeFilterView->getTypes() as $type) {
80
                $statistics[] = [
81
                    'type' => $type,
82
                    'count' => $this->repository->countUsers($type)
83
                ];
84
            }
85
        }
86
87
        return $this->render($template, [
88
            'users' => $paginator->getIterator(),
89
            'page' => $page,
90
            'pages' => $pages,
91
            'grade' => $grade,
92
            'grades' => $grades,
93
            'q' => $q,
94
            'statistics' => $statistics,
95
            'count' => $this->repository->countUsers(),
96
            'roleFilter' => $roleFilterView,
97
            'typeFilter' => $typeFilterView,
98
            'withoutParents' => $withoutParents,
99
            'csrf_id' => static::CsrfTokenId,
100
            'csrf_key' => static::CsrfTokenKey
101
        ]);
102
    }
103
104
    #[Route(path: '/users', name: 'users')]
105
    public function index(Request $request, UserTypeFilter $typeFilter, UserRoleFilter $roleFilter): Response {
106
        return $this->internalDisplay($request, $typeFilter, $roleFilter, false);
107
    }
108
109
    #[Route(path: '/users/trash', name: 'users_trash')]
110
    public function trash(Request $request, UserTypeFilter $typeFilter, UserRoleFilter $roleFilter): Response {
111
        return $this->internalDisplay($request, $typeFilter, $roleFilter, true);
112
    }
113
114
    #[Route(path: '/users/{uuid}/attributes', name: 'show_attributes')]
115
    public function showAttributes(User $user, AttributeResolver $resolver, AttributeValueProvider $provider): Response {
116
        $attributes = $resolver->getDetailedResultingAttributeValuesForUser($user);
117
        $defaultAttributes = $provider->getCommonAttributesForUser($user);
118
119
        return $this->render('users/attributes.html.twig', [
120
            'selectedUser' => $user,
121
            'attributes' => $attributes,
122
            'defaultAttributes' => $defaultAttributes
123
        ]);
124
    }
125
126
    #[Route(path: '/users/add', name: 'add_user')]
127
    public function add(Request $request, AttributePersister $attributePersister, UserPasswordHasherInterface $passwordHasher): Response {
128
        $user = new User();
129
130
        $form = $this->createForm(UserType::class, $user);
131
        $form->handleRequest($request);
132
133
        if($form->isSubmitted() && $form->isValid()) {
134
            $user->setPassword($passwordHasher->hashPassword($user, $form->get('group_password')->get('password')->getData()));
135
136
            $this->repository->persist($user);
137
138
            $attributeData = $this->getAttributeData($form);
139
            $attributePersister->persistUserAttributes($attributeData, $user);
140
141
            $this->addFlash('success', 'users.add.success');
142
            return $this->redirectToRoute('users');
143
        }
144
145
        return $this->render('users/add.html.twig', [
146
            'form' => $form->createView()
147
        ]);
148
    }
149
150
    #[Route(path: '/users/{uuid}/edit', name: 'edit_user')]
151
    public function edit(Request $request, User $user, AttributePersister $attributePersister, UserPasswordHasherInterface $passwordHasher): Response {
152
        if($user->isDeleted()) {
153
            throw new NotFoundHttpException();
154
        }
155
156
        $form = $this->createForm(UserType::class, $user);
157
        $form->handleRequest($request);
158
159
        if($form->isSubmitted() && $form->isValid()) {
160
            if($form->has('group_password')) {
161
                $password = $form->get('group_password')->get('password')->getData();
162
163
                if (!empty($password) && !$user instanceof ActiveDirectoryUser) {
164
                    $user->setPassword($passwordHasher->hashPassword($user, $password));
165
                }
166
            }
167
168
            $this->repository->persist($user);
169
170
            $attributeData = $this->getAttributeData($form);
171
            $attributePersister->persistUserAttributes($attributeData, $user);
172
173
            $this->addFlash('success', 'users.edit.success');
174
            return $this->redirectToRoute('users');
175
        }
176
177
        return $this->render('users/edit.html.twig', [
178
            'form' => $form->createView(),
179
            'requestedUser' => $user
180
        ]);
181
    }
182
183
    #[Route(path: '/users/{uuid}/remove', name: 'remove_user')]
184
    public function remove(User $user, Request $request, TranslatorInterface $translator): Response {
185
        if($this->getUser() instanceof User && $this->getUser()->getId() === $user->getId()) {
186
            $this->addFlash('error', 'users.remove.error.self');
187
            return $this->redirectToRoute('users');
188
        }
189
190
        if($user->isDeleted() === false) {
191
            if($request->isMethod('POST') && $this->isCsrfTokenValid(static::CsrfTokenId, $request->request->get(static::CsrfTokenKey))) {
192
                $this->addFlash('success', 'users.remove.trash.success');
193
                $this->repository->remove($user);
194
                return $this->redirectToRoute('users');
195
            } else {
196
                $this->addFlash('error', 'users.remove.trash.error');
197
                return $this->redirectToRoute('users');
198
            }
199
        }
200
201
        $form = $this->createForm(ConfirmType::class, [], [
202
            'message' => $translator->trans('users.remove.confirm', [
203
                '%username%' => $user->getUsername(),
204
                '%firstname%' => $user->getFirstname(),
205
                '%lastname%' => $user->getLastname()
206
            ]),
207
            'label' => 'users.remove.label'
208
        ]);
209
        $form->handleRequest($request);
210
211
        if($form->isSubmitted() && $form->isValid()) {
212
            $this->repository->remove($user);
213
214
            $this->addFlash('success', 'users.remove.success');
215
            return $this->redirectToRoute('users');
216
        }
217
218
        return $this->render('users/remove.html.twig', [
219
            'form' => $form->createView(),
220
            'requestedUser' => $user
221
        ]);
222
    }
223
224
    #[Route(path: '/users/{uuid}/restore', name: 'restore_user', methods: ['POST'])]
225
    public function restore(User $user, Request $request): Response {
226
        if($this->getUser() instanceof User && $this->getUser()->getId() === $user->getId()) {
227
            $this->addFlash('error', 'users.restore.error.self');
228
            return $this->redirectToRoute('users');
229
        }
230
231
        if($request->isMethod('POST') && $this->isCsrfTokenValid(static::CsrfTokenId, $request->request->get(static::CsrfTokenKey))) {
232
            $this->addFlash('success', 'users.trash.restore.success');
233
            $user->setDeletedAt(null);
234
            $this->repository->persist($user);
235
            return $this->redirectToRoute('users_trash');
236
        }
237
238
        $this->addFlash('error', 'users.trash.restore.error');
239
        return $this->redirectToRoute('users_trash');
240
    }
241
242
    #[Route(path: '/users/{uuid}/reset_password_ad', name: 'reset_password_ad')]
243
    public function resetPasswordActiveDirectory(Request $request, User $user, AdAuthInterface $adAuth, TranslatorInterface $translator): Response {
244
        if(!$user instanceof ActiveDirectoryUser) {
245
            $this->addFlash('error', 'users.reset_pw_ad.cannot_change');
246
            return $this->redirectToRoute('users');
247
        }
248
249
        $form = $this->createForm(ResetPasswortActiveDirectoryType::class, [
250
            'username' => $user->getUserIdentifier()
251
        ]);
252
        $form->handleRequest($request);
253
254
        if($form->isSubmitted() && $form->isValid()) {
255
            $adminUsername = $form->get('admin_username')->getData();
256
            $adminPassword = $form->get('admin_password')->getData();
257
            $username = $user->getUserIdentifier();
258
            $password = $form->get('password')->getData();
259
260
            try {
261
                $response = $adAuth->resetPassword(
262
                    new Credentials($username, $password),
263
                    new Credentials($adminUsername, $adminPassword)
264
                );
265
266
                if($response instanceof PasswordSuccessResponse) {
267
                    $this->addFlash('success', 'users.reset_pw_ad.success');
268
                    return $this->redirectToRoute('users');
269
                } else if($response instanceof PasswordFailedResponse) {
270
                    $this->addFlash('error', $translator->trans('users.reset_pw_ad.failure', [
271
                        '%error%' => $response->getResult()
272
                    ]));
273
                }
274
            } catch (SocketException $e) {
275
                $this->addFlash('error', $translator->trans('users.reset_pw_ad.failure', [
276
                    '%error%' => $e->getMessage()
277
                ]));
278
            }
279
        }
280
281
        return $this->render('users/reset_pw_ad.html.twig', [
282
            'user' => $user,
283
            'form' => $form->createView()
284
        ]);
285
    }
286
287
    #[Route(path: '/users/{uuid}/reset_password', name: 'reset_password')]
288
    public function resetPassword(Request $request, User $user, ForgotPasswordManager $manager): Response {
289
        if($manager->canResetPassword($user, '[email protected]') === false) {
290
            $this->addFlash('error', 'users.reset_pw.cannot_change');
291
            return $this->redirectToRoute('users');
292
        }
293
294
        $form = $this->createForm(ResetPasswordType::class, [
295
            'email' => $user->getEmail()
296
        ]);
297
        $form->handleRequest($request);
298
299
        if($form->isSubmitted() && $form->isValid()) {
300
            $email = $form->get('email')->getData();
301
            $manager->resetPassword($user, $email);
302
            $this->addFlash('success', 'forgot_pw.request.success');
303
            return $this->redirectToRoute('users');
304
        }
305
306
        return $this->render('users/reset_pw.html.twig', [
307
            'user' => $user,
308
            'form'=> $form->createView()
309
        ]);
310
    }
311
312
    #[Route('/users/{uuid}/logout', name: 'user_logout_everywhere')]
313
    public function logout(User $user, LogoutHelper $logoutHelper, RefererHelper $refererHelper): Response {
314
        $logoutHelper->logout($user);
315
316
        $this->addFlash('success', 'sessions.logout_everywhere.success');
317
        return $this->redirect(
318
            $refererHelper->getRefererPathFromRequest('users')
319
        );
320
    }
321
}