Passed
Push — master ( f9dfb3...04c6d5 )
by Jan
06:08
created

UserController::getGravatar()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 3
nop 6
dl 0
loc 18
rs 9.9
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as published
9
 * by the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
declare(strict_types=1);
22
23
namespace App\Controller;
24
25
use App\DataTables\LogDataTable;
26
use App\Entity\Attachments\UserAttachment;
27
use App\Entity\Base\AbstractNamedDBElement;
28
use App\Entity\Parameters\AbstractParameter;
29
use App\Entity\UserSystem\User;
30
use App\Events\SecurityEvent;
31
use App\Events\SecurityEvents;
32
use App\Form\Permissions\PermissionsType;
33
use App\Form\UserAdminForm;
34
use App\Services\ImportExportSystem\EntityExporter;
35
use App\Services\ImportExportSystem\EntityImporter;
36
use App\Services\Trees\StructuralElementRecursionHelper;
37
use App\Services\UserSystem\PermissionPresetsHelper;
38
use App\Services\UserSystem\PermissionSchemaUpdater;
39
use Doctrine\ORM\EntityManagerInterface;
40
use Exception;
41
use InvalidArgumentException;
42
use Omines\DataTablesBundle\DataTableFactory;
43
use Symfony\Component\Asset\Packages;
44
use Symfony\Component\Form\FormInterface;
45
use Symfony\Component\HttpFoundation\RedirectResponse;
46
use Symfony\Component\HttpFoundation\Request;
47
use Symfony\Component\HttpFoundation\Response;
48
use Symfony\Component\Routing\Annotation\Route;
49
50
/**
51
 * @Route("/user")
52
 * Class UserController
53
 */
54
class UserController extends AdminPages\BaseAdminController
55
{
56
    protected $entity_class = User::class;
57
    protected $twig_template = 'AdminPages/UserAdmin.html.twig';
58
    protected $form_class = UserAdminForm::class;
59
    protected $route_base = 'user';
60
    protected $attachment_class = UserAttachment::class;
61
    //Just define a value here to prevent error. It is not used.
62
    protected $parameter_class = AbstractParameter::class;
63
64
    protected function additionalActionEdit(FormInterface $form, AbstractNamedDBElement $entity): bool
65
    {
66
        //Check if we editing a user and if we need to change the password of it
67
        if ($entity instanceof User && !empty($form['new_password']->getData())) {
68
            $password = $this->passwordEncoder->hashPassword($entity, $form['new_password']->getData());
69
            $entity->setPassword($password);
70
            //By default the user must change the password afterwards
71
            $entity->setNeedPwChange(true);
72
73
            $event = new SecurityEvent($entity);
74
            $this->eventDispatcher->dispatch($event, SecurityEvents::PASSWORD_CHANGED);
75
        }
76
77
        return true;
78
    }
79
80
    /**
81
     * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="user_edit")
82
     * @Route("/{id}/", requirements={"id"="\d+"})
83
     *
84
     * @throws Exception
85
     */
86
    public function edit(User $entity, Request $request, EntityManagerInterface $em,  PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response
87
    {
88
        //Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet)
89
        $permissionSchemaUpdater->userUpgradeSchemaRecursively($entity);
90
91
        //Handle 2FA disabling
92
        if ($request->request->has('reset_2fa')) {
93
            //Check if the admin has the needed permissions
94
            $this->denyAccessUnlessGranted('set_password', $entity);
95
            if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) {
96
                //Disable Google authenticator
97
                $entity->setGoogleAuthenticatorSecret(null);
98
                $entity->setBackupCodes([]);
99
                //Remove all U2F keys
100
                foreach ($entity->getLegacyU2FKeys() as $key) {
101
                    $em->remove($key);
102
                }
103
                foreach ($entity->getWebAuthnKeys() as $key) {
104
                    $em->remove($key);
105
                }
106
                //Invalidate trusted devices
107
                $entity->invalidateTrustedDeviceTokens();
108
                $em->flush();
109
110
                $event = new SecurityEvent($entity);
111
                $this->eventDispatcher->dispatch($event, SecurityEvents::TFA_ADMIN_RESET);
112
113
                $this->addFlash('success', 'user.edit.reset_success');
114
            } else {
115
                $this->addFlash('danger', 'csfr_invalid');
116
            }
117
        }
118
119
        //Handle permissions presets
120
        if ($request->request->has('permission_preset')) {
121
            $this->denyAccessUnlessGranted('edit_permissions', $entity);
122
            if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) {
123
                $preset = $request->request->get('permission_preset');
124
125
                $permissionPresetsHelper->applyPreset($entity, $preset);
126
127
                $em->flush();
128
129
                $this->addFlash('success', 'user.edit.permission_success');
130
131
                //We need to stop the execution here, or our permissions changes will be overwritten by the form values
132
                return $this->redirectToRoute('user_edit', ['id' => $entity->getID()]);
133
            } else {
134
                $this->addFlash('danger', 'csfr_invalid');
135
            }
136
        }
137
138
        return $this->_edit($entity, $request, $em, $timestamp);
139
    }
140
141
    protected function additionalActionNew(FormInterface $form, AbstractNamedDBElement $entity): bool
142
    {
143
        if ($entity instanceof User && !empty($form['new_password']->getData())) {
144
            $password = $this->passwordEncoder->hashPassword($entity, $form['new_password']->getData());
145
            $entity->setPassword($password);
146
            //By default the user must change the password afterwards
147
            $entity->setNeedPwChange(true);
148
        }
149
150
        return true;
151
    }
152
153
    /**
154
     * @Route("/new", name="user_new")
155
     * @Route("/{id}/clone", name="user_clone")
156
     * @Route("/")
157
     */
158
    public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?User $entity = null): Response
159
    {
160
        return $this->_new($request, $em, $importer, $entity);
161
    }
162
163
    /**
164
     * @Route("/{id}", name="user_delete", methods={"DELETE"}, requirements={"id"="\d+"})
165
     */
166
    public function delete(Request $request, User $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
167
    {
168
        if (User::ID_ANONYMOUS === $entity->getID()) {
169
            throw new InvalidArgumentException('You can not delete the anonymous user! It is needed for permission checking without a logged in user');
170
        }
171
172
        return $this->_delete($request, $entity, $recursionHelper);
173
    }
174
175
    /**
176
     * @Route("/export", name="user_export_all")
177
     */
178
    public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
179
    {
180
        return $this->_exportAll($em, $exporter, $request);
181
    }
182
183
    /**
184
     * @Route("/{id}/export", name="user_export")
185
     */
186
    public function exportEntity(User $entity, EntityExporter $exporter, Request $request): Response
187
    {
188
        return $this->_exportEntity($entity, $exporter, $request);
189
    }
190
191
    /**
192
     * @Route("/info", name="user_info_self")
193
     * @Route("/{id}/info", name="user_info")
194
     */
195
    public function userInfo(?User $user, Packages $packages, Request $request, DataTableFactory $dataTableFactory): Response
0 ignored issues
show
Unused Code introduced by
The parameter $dataTableFactory is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

195
    public function userInfo(?User $user, Packages $packages, Request $request, /** @scrutinizer ignore-unused */ DataTableFactory $dataTableFactory): Response

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $packages is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

195
    public function userInfo(?User $user, /** @scrutinizer ignore-unused */ Packages $packages, Request $request, DataTableFactory $dataTableFactory): Response

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
196
    {
197
        //If no user id was passed, then we show info about the current user
198
        if (null === $user) {
199
            $tmp = $this->getUser();
200
            if (!$tmp instanceof User) {
201
                throw new InvalidArgumentException('Userinfo only works for database users!');
202
            }
203
            $user = $tmp;
204
        } else {
205
            //Else we must check, if the current user is allowed to access $user
206
            $this->denyAccessUnlessGranted('read', $user);
207
        }
208
209
        $table = $this->dataTableFactory->createFromType(
210
            LogDataTable::class,
211
            [
212
                'filter_elements' => $user,
213
                'mode' => 'element_history',
214
            ],
215
            ['pageLength' => 10]
216
        )
217
            ->handleRequest($request);
218
219
        if ($table->isCallback()) {
220
            return $table->getResponse();
221
        }
222
223
        //Show permissions to user
224
        $builder = $this->createFormBuilder()->add('permissions', PermissionsType::class, [
225
            'mapped' => false,
226
            'disabled' => true,
227
            'inherit' => true,
228
            'data' => $user,
229
        ]);
230
231
        return $this->renderForm('Users/user_info.html.twig', [
232
            'user' => $user,
233
            'form' => $builder->getForm(),
234
            'datatable' => $table,
235
        ]);
236
    }
237
}
238