UserController::trash()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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