Passed
Pull Request — master (#70)
by Jan
11:26
created

UserController::deleteCheck()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

208
    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...
209
    {
210
        //If no user id was passed, then we show info about the current user
211
        if (null === $user) {
212
            $tmp = $this->getUser();
213
            if (! $tmp instanceof User) {
214
                throw new InvalidArgumentException('Userinfo only works for database users!');
215
            }
216
            $user = $tmp;
217
        } else {
218
            //Else we must check, if the current user is allowed to access $user
219
            $this->denyAccessUnlessGranted('read', $user);
220
        }
221
222
        $table = $this->dataTableFactory->createFromType(
223
            LogDataTable::class,
224
            [
225
                'filter_elements' => $user,
226
                'mode' => 'element_history',
227
            ],
228
            ['pageLength' => 10]
229
        )
230
            ->handleRequest($request);
231
232
        if ($table->isCallback()) {
233
            return $table->getResponse();
234
        }
235
236
        if ($this->getParameter('partdb.users.use_gravatar')) {
237
            $avatar = $this->getGravatar($user->getEmail(), 200, 'identicon');
238
        } else {
239
            $avatar = $packages->getUrl('/img/default_avatar.png');
240
        }
241
242
        //Show permissions to user
243
        $builder = $this->createFormBuilder()->add('permissions', PermissionsType::class, [
244
            'mapped' => false,
245
            'disabled' => true,
246
            'inherit' => true,
247
            'data' => $user,
248
        ]);
249
250
        return $this->render('Users/user_info.html.twig', [
251
            'user' => $user,
252
            'avatar' => $avatar,
253
            'form' => $builder->getForm()->createView(),
254
            'datatable' => $table,
255
        ]);
256
    }
257
258
    /**
259
     * Get either a Gravatar URL or complete image tag for a specified email address.
260
     *
261
     * @param string $email The email address
262
     * @param int    $s     Size in pixels, defaults to 80px [ 1 - 2048 ]
263
     * @param string $d     Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
264
     * @param string $r     Maximum rating (inclusive) [ g | pg | r | x ]
265
     * @param bool   $img   True to return a complete IMG tag False for just the URL
266
     * @param array  $atts  Optional, additional key/value attributes to include in the IMG tag
267
     *
268
     * @return string containing either just a URL or a complete image tag
269
     * @source https://gravatar.com/site/implement/images/php/
270
     */
271
    public function getGravatar(?string $email, int $s = 80, string $d = 'mm', string $r = 'g', bool $img = false, array $atts = []): string
272
    {
273
        if (null === $email) {
274
            return '';
275
        }
276
277
        $url = 'https://www.gravatar.com/avatar/';
278
        $url .= md5(strtolower(trim($email)));
279
        $url .= "?s=${s}&d=${d}&r=${r}";
280
        if ($img) {
281
            $url = '<img src="'.$url.'"';
282
            foreach ($atts as $key => $val) {
283
                $url .= ' '.$key.'="'.$val.'"';
284
            }
285
            $url .= ' />';
286
        }
287
288
        return $url;
289
    }
290
}
291