UserController.php 38.2 KB
Newer Older
Florian Azizian's avatar
Florian Azizian committed
1
2
3
<?php

/**
4
5
* Copyright Maarch since 2008 under license.
* See LICENSE.txt file at the root folder for more details.
Florian Azizian's avatar
Florian Azizian committed
6
7
8
9
10
11
12
13
14
15
16
* This file is part of Maarch software.
*
*/

/**
* @brief User Controller
* @author dev@maarch.org
*/

namespace User\controllers;

17
use Configuration\models\ConfigurationModel;
18
19
use Document\controllers\DigitalSignatureController;
use Document\models\DocumentModel;
Damien's avatar
Damien committed
20
use Email\controllers\EmailController;
Damien's avatar
Damien committed
21
use Firebase\JWT\JWT;
22
use Group\controllers\PrivilegeController;
23
use Group\models\GroupModel;
24
use Group\models\GroupPrivilegeModel;
Damien's avatar
Damien committed
25
use History\controllers\HistoryController;
Florian Azizian's avatar
Florian Azizian committed
26
27
28
use Respect\Validation\Validator;
use Slim\Http\Request;
use Slim\Http\Response;
Damien's avatar
Damien committed
29
use SrcCore\controllers\AuthenticationController;
Damien's avatar
Damien committed
30
use SrcCore\controllers\LanguageController;
Damien's avatar
Damien committed
31
32
use SrcCore\controllers\PasswordController;
use SrcCore\models\AuthenticationModel;
Damien's avatar
Damien committed
33
use SrcCore\models\CoreConfigModel;
34
use SrcCore\models\PasswordModel;
Damien's avatar
Damien committed
35
use SrcCore\models\ValidatorModel;
36
use User\models\SignatureModel;
37
use User\models\UserGroupModel;
Florian Azizian's avatar
Florian Azizian committed
38
use User\models\UserModel;
Damien's avatar
Damien committed
39
use Workflow\models\WorkflowModel;
40
use Workflow\models\WorkflowTemplateItemModel;
41
use Workflow\models\WorkflowTemplateModel;
42
use Notification\models\NotificationsScheduleModel;
Florian Azizian's avatar
Florian Azizian committed
43
44
45

class UserController
{
Damien's avatar
Damien committed
46
47
    public function get(Request $request, Response $response)
    {
Damien's avatar
Damien committed
48
        $queryParams = $request->getQueryParams();
Damien's avatar
Damien committed
49

Damien's avatar
Damien committed
50
        $select = ['id', 'firstname', 'lastname', 'email', 'phone', 'substitute', 'x509_fingerprint'];
Damien's avatar
Damien committed
51
52
53
54
55
        $where = [];
        $queryData = [];
        if (empty($queryParams['mode'])) {
            $where = ['"isRest" = ?'];
            $queryData = ['false'];
56
57
58
        }
        if (PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
            $select[] = 'login';
Damien's avatar
Damien committed
59
60
        }

Damien's avatar
Damien committed
61
        $users = UserModel::get([
62
            'select'    => $select,
63
            'where'     => $where,
Damien's avatar
Damien committed
64
            'data'      => $queryData,
Damien's avatar
Damien committed
65
            'orderBy'   => ['lastname', 'firstname']
Damien's avatar
Damien committed
66
67
        ]);

68
69
        $currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);

70
71
        foreach ($users as $key => $user) {
            $users[$key]['substitute'] = !empty($user['substitute']);
72
73
74
75
            if ($currentUser['isRest']) {
                $users[$key]['x509Fingerprint'] = $users[$key]['x509_fingerprint'];
            }
            unset($users[$key]['x509_fingerprint']);
76
77
        }

Damien's avatar
Damien committed
78
79
80
        return $response->withJson(['users' => $users]);
    }

81
82
    public function getById(Request $request, Response $response, array $args)
    {
83
84
85
86
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

87
88
89
        if ($GLOBALS['id'] == $args['id'] || PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
            $user = UserController::getUserInformationsById(['id' => $args['id']]);
        } else {
90
            $user = UserModel::getById(['select' => ['id', 'firstname', 'lastname', 'email', 'phone', 'substitute'], 'id' => $args['id']]);
91
        }
92

93
94
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
95
        }
Florian Azizian's avatar
Florian Azizian committed
96

97
98
99
100
101
        if ($GLOBALS['id'] == $args['id'] || PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
            $user['groups'] = [];
            $userGroups = UserGroupModel::get(['select' => ['group_id'], 'where' => ['user_id = ?'], 'data' => [$args['id']]]);
            $groupsIds  = array_column($userGroups, 'group_id');
            if (!empty($groupsIds)) {
102
                $groups = GroupModel::get(['select' => ['label', 'id'], 'where' => ['id in (?)'], 'data' => [$groupsIds]]);
103
104
                $user['groups'] = $groups;
            }
105
106
107
108
109
110
111
112
113
114
        }

        HistoryController::add([
            'code'          => 'OK',
            'objectType'    => 'users',
            'objectId'      => $args['id'],
            'type'          => 'VIEW',
            'message'       => "{userViewed} : {$user['firstname']} {$user['lastname']}"
        ]);

Florian Azizian's avatar
Florian Azizian committed
115
        return $response->withJson(['user' => $user]);
116
117
    }

Florian Azizian's avatar
Florian Azizian committed
118
119
    public function create(Request $request, Response $response)
    {
120
        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
121
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
Damien's avatar
Damien committed
122
123
        }

Damien's avatar
Damien committed
124
125
126
127
        $body = $request->getParsedBody();

        if (empty($body)) {
            return $response->withStatus(400)->withJson(['errors' => 'Body is not set or empty']);
128
        } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['login']) || !preg_match("/^[\w.@-]*$/", $body['login'])) {
129
            return $response->withStatus(400)->withJson(['errors' => 'Body login is empty, not a string or wrong formatted']);
130
        } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['firstname'])) {
Damien's avatar
Damien committed
131
            return $response->withStatus(400)->withJson(['errors' => 'Body firstname is empty or not a string']);
132
        } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['lastname'])) {
Damien's avatar
Damien committed
133
            return $response->withStatus(400)->withJson(['errors' => 'Body lastname is empty or not a string']);
134
        } elseif (empty($body['email']) || !filter_var($body['email'], FILTER_VALIDATE_EMAIL) || !Validator::stringType()->notEmpty()->length(1, 128)->validate($body['email'])) {
Damien's avatar
Damien committed
135
            return $response->withStatus(400)->withJson(['errors' => 'Body email is empty or not a valid email']);
136
137
        } elseif (!empty($body['x509Fingerprint']) && !Validator::stringType()->validate($body['x509Fingerprint'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body x509Fingerprint is not a string']);
138
139
        } elseif (!Validator::arrayType()->notEmpty()->validate($body['groups'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body groups is empty or not an array']);
Florian Azizian's avatar
Florian Azizian committed
140
141
        }

142
143
        $body['groups'] = array_column($body['groups'], 'id');

144
        $body['login'] = strtolower($body['login']);
Damien's avatar
Damien committed
145
        $existingUser = UserModel::getByLogin(['login' => $body['login'], 'select' => [1]]);
Florian Azizian's avatar
Florian Azizian committed
146
        if (!empty($existingUser)) {
147
            return $response->withStatus(400)->withJson(['errors' => 'Login already exists', 'lang' => 'userLoginAlreadyExists']);
Florian Azizian's avatar
Florian Azizian committed
148
        }
Damien's avatar
Damien committed
149

150
        $body['x509_fingerprint'] = $body['x509Fingerprint'];
Florian Azizian's avatar
Florian Azizian committed
151

Damien's avatar
Damien committed
152
        if (!empty($body['isRest'])) {
153
            $body['"isRest"'] = true;
Florian Azizian's avatar
Florian Azizian committed
154
        }
155
156
157
158
        if (empty($body['picture'])) {
            $body['picture'] = base64_encode(file_get_contents('src/frontend/assets/user_picture.png'));
            $body['picture'] = 'data:image/png;base64,' . $body['picture'];
        }
Florian Azizian's avatar
Florian Azizian committed
159

160
161
162
163
        if (!empty($body['signatureModes'])) {
            if (!Validator::arrayType()->validate($body['signatureModes'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Body signatureModes is not an array']);
            } else {
164
                $body['signatureModes'] = array_unique($body['signatureModes']);
165
166
167
                foreach ($body['signatureModes'] as $key => $signatureMode) {
                    if (!SignatureController::isValidSignatureMode(['mode' => $signatureMode])) {
                        return $response->withStatus(400)->withJson(['errors' => "Body signatureModes[{$key}] is not a valid signature mode"]);
168
169
170
171
                    }
                }
            }
        } else {
172
            $body['signatureModes'] = ['stamp'];
173
174
175
        }
        $body['signatureModes'] = json_encode($body['signatureModes']);

Damien's avatar
Damien committed
176
        $id = UserModel::create($body);
Florian Azizian's avatar
Florian Azizian committed
177
178

        HistoryController::add([
Damien's avatar
Damien committed
179
180
181
182
            'code'          => 'OK',
            'objectType'    => 'users',
            'objectId'      => $id,
            'type'          => 'CREATION',
Damien's avatar
Damien committed
183
            'message'       => "{userAdded} : {$body['firstname']} {$body['lastname']}"
Florian Azizian's avatar
Florian Azizian committed
184
185
        ]);

186
187
188
189
        if (empty($body['isRest'])) {
            AuthenticationController::sendAccountActivationNotification(['userId' => $id, 'userEmail' => $body['email']]);
        }

Damien's avatar
Damien committed
190
        return $response->withJson(['id' => $id]);
Florian Azizian's avatar
Florian Azizian committed
191
192
    }

Damien's avatar
Damien committed
193
194
    public function update(Request $request, Response $response, array $args)
    {
195
196
        $connection = ConfigurationModel::getConnection();
        if (($GLOBALS['id'] != $args['id'] || $connection != 'default') && !PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
197
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
Damien's avatar
Damien committed
198
199
        }

Damien's avatar
Damien committed
200
        $body = $request->getParsedBody();
201

202
203
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
204
        } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['firstname'])) {
205
            return $response->withStatus(400)->withJson(['errors' => 'Body firstname is empty or not a string']);
206
        } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['lastname'])) {
207
            return $response->withStatus(400)->withJson(['errors' => 'Body lastname is empty or not a string']);
208
        } elseif (empty($body['email']) || !filter_var($body['email'], FILTER_VALIDATE_EMAIL) || !Validator::stringType()->notEmpty()->length(1, 128)->validate($body['email'])) {
Damien's avatar
Damien committed
209
            return $response->withStatus(400)->withJson(['errors' => 'Body email is empty or not a valid email']);
210
211
        } elseif (!empty($body['x509Fingerprint']) && !Validator::stringType()->validate($body['x509Fingerprint'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body x509Fingerprint is not a string']);
Damien's avatar
Damien committed
212
213
        }

214
        $user = UserModel::getById(['id' => $args['id'], 'select' => [1]]);
215
216
217
218
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

219
        $set = [
220
221
222
            'firstname'       => $body['firstname'],
            'lastname'        => $body['lastname'],
            'email'           => $body['email'],
Damien's avatar
Damien committed
223
            'phone'           => $body['phone'],
224
            'signature_modes' => []
225
226
        ];

227
228
229
230
231
        $currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);
        if ($currentUser['isRest']) {
            $set['x509_fingerprint'] = $body['x509Fingerprint'];
        }

232
233
        if (!empty($body['signatureModes'])) {
            if (!Validator::arrayType()->validate($body['signatureModes'])) {
234
                return $response->withStatus(400)->withJson(['errors' => 'Body signatureModes is not an array']);
235
            }
236
            $body['signatureModes'] = array_unique($body['signatureModes']);
237
238
239
240
            $modes = [];
            foreach ($body['signatureModes'] as $signatureMode) {
                if (SignatureController::isValidSignatureMode(['mode' => $signatureMode])) {
                    $modes[] = $signatureMode;
241
242
                }
            }
243
244
245
246
247
248
249
250
251
            $validModes = CoreConfigModel::getSignatureModes();
            $higherMode = 'stamp';
            foreach ($validModes as $validMode) {
                if (in_array($validMode['id'], $modes)) {
                    $higherMode = $validMode['id'];
                    break;
                }
            }

252
            WorkflowModel::update([
253
                'set'   => ['signature_mode' => $higherMode],
254
                'where' => ['user_id = ?', 'signature_mode not in (?)', 'process_date is null', 'signature_mode != ?'],
255
                'data'  => [$args['id'], $modes, $higherMode]
256
257
            ]);
            WorkflowTemplateItemModel::update([
258
                'set'   => ['signature_mode' => $higherMode],
259
                'where' => ['user_id = ?', 'signature_mode not in (?)', 'signature_mode != ?'],
260
                'data'  => [$args['id'], $modes, $higherMode]
261
262
            ]);

263
            $set['signature_modes'] = $modes;
264
265
266
        }
        $set['signature_modes'] = json_encode($set['signature_modes']);

Damien's avatar
Damien committed
267
268
269
270
271
        UserModel::update([
            'set'   => $set,
            'where' => ['id = ?'],
            'data'  => [$args['id']]
        ]);
Damien's avatar
Damien committed
272

Damien's avatar
Damien committed
273
        HistoryController::add([
Damien's avatar
Damien committed
274
275
276
277
            'code'          => 'OK',
            'objectType'    => 'users',
            'objectId'      => $args['id'],
            'type'          => 'MODIFICATION',
Damien's avatar
Damien committed
278
            'message'       => "{userUpdated} : {$body['firstname']} {$body['lastname']}"
Damien's avatar
Damien committed
279
280
        ]);

Damien's avatar
Damien committed
281
        return $response->withJson(['user' => UserController::getUserInformationsById(['id' => $args['id']])]);
Damien's avatar
Damien committed
282
283
    }

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    public function updatePicture(Request $request, Response $response, array $args)
    {
        if ($GLOBALS['id'] != $args['id']) {
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

        $body = $request->getParsedBody();

        if (!Validator::stringType()->notEmpty()->validate($body['picture'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body picture is empty']);
        }

        $user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

        $infoContent = '';
        if (preg_match('/^data:image\/(\w+);base64,/', $body['picture'])) {
            $infoContent = substr($body['picture'], 0, strpos($body['picture'], ',') + 1);
            $body['picture'] = substr($body['picture'], strpos($body['picture'], ',') + 1);
        }
306
307
308
309
        $picture  = base64_decode($body['picture']);
        $finfo    = new \finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->buffer($picture);
        $type     = explode('/', $mimeType);
310
311
312
313
314

        if ($type[0] != 'image') {
            return $response->withStatus(400)->withJson(['errors' => 'Picture is not an image']);
        }

315
316
        $imagick = new \Imagick();
        $imagick->readImageBlob(base64_decode($body['picture']));
317
318
319
        if (!empty($body['pictureOrientation'])) {
            $imagick->rotateImage(new \ImagickPixel(), $body['pictureOrientation']);
        }
320
        $imagick->thumbnailImage(100, null);
321
        $body['picture'] = base64_encode($imagick->getImagesBlob());
322
323

        $set = [
324
            'picture' => $infoContent . $body['picture']
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
        ];

        UserModel::update([
            'set'   => $set,
            'where' => ['id = ?'],
            'data'  => [$args['id']]
        ]);

        HistoryController::add([
            'code'          => 'OK',
            'objectType'    => 'users',
            'objectId'      => $args['id'],
            'type'          => 'MODIFICATION',
            'message'       => "{userUpdated} : {$user['firstname']} {$user['lastname']}"
        ]);

341
        return $response->withStatus(204);
342
343
    }

Damien's avatar
Damien committed
344
345
    public function delete(Request $request, Response $response, array $args)
    {
346
        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users']) || $GLOBALS['id'] == $args['id']) {
Damien's avatar
Damien committed
347
348
349
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

350
351
352
353
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

Damien's avatar
Damien committed
354
355
356
357
358
        $user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

359
360
361
        $substitutedUsers = UserModel::get(['select' => ['id'], 'where' => ['substitute = ?'], 'data' => [$args['id']]]);
        $allSubstitutedUsers = array_column($substitutedUsers, 'id');

Damien's avatar
Damien committed
362
        $workflows = WorkflowModel::get([
363
364
            'select' => ['main_document_id'],
            'where'  => ['user_id = ?', "process_date IS NULL", "status IS NULL"],
365
366
367
            'data'   => [$args['id']]
        ]);

368
        $mainDocumentId = array_column($workflows, 'main_document_id');
Damien's avatar
Damien committed
369

370
371
372
373
374
375
        if (!empty($mainDocumentId)) {
            $workflows = WorkflowModel::get([
                'select' => ['id', 'digital_signature_id', 'main_document_id'],
                'where'  => ['main_document_id in (?)', "process_date IS NULL", "status IS NULL"],
                'data'   => [$mainDocumentId]
            ]);
Damien's avatar
Damien committed
376

377
378
379
380
381
382
            $workflowsId = array_column($workflows, 'id');
            WorkflowModel::update([
                'set'   => ['status' => 'STOP', 'process_date' => 'CURRENT_TIMESTAMP'],
                'where' => ['id in (?)', "process_date IS NULL AND status IS NULL"],
                'data'  => [$workflowsId]
            ]);
Damien's avatar
Damien committed
383

384
385
386
387
388
389
390
391
            $previousDocumentId = null;
            foreach ($workflows as $step) {
                $document = DocumentModel::getById(['select' => ['typist', 'id'], 'id' => $step['main_document_id']]);
                if (!empty($step['digital_signature_id'])) {
                    DigitalSignatureController::abort(['signatureId' => $step['digital_signature_id'], 'documentId' => $args['id']]);
                    break;
                }
                if ($previousDocumentId != $step['main_document_id']) {
392
                    EmailController::sendOrFillNotificationToTypist(['documentId' => $document['id'], 'senderId' => $GLOBALS['id'], 'recipientId' => $document['typist'], 'mode' => 'DEL']);
393
394
                }
                $previousDocumentId = $step['main_document_id'];
395
396
            }
        }
Damien's avatar
Damien committed
397
398
399
400
401
402

        //Substituted Users
        if (!empty($allSubstitutedUsers)) {
            UserModel::update(['set' => ['substitute' => null], 'where' => ['id in (?)'], 'data' => [$allSubstitutedUsers]]);
        }

403
404
405
406
407
        $workflowTemplates = WorkflowTemplateModel::get([
            'select' => ['id'],
            'where'  => ['owner = ?'],
            'data'   => [$args['id']]
        ]);
Damien's avatar
Damien committed
408

409
410
411
412
413
414
415
416
417
        if (!empty($workflowTemplates)) {
            $workflowTemplates = array_column($workflowTemplates, 'id');
            WorkflowTemplateItemModel::delete(['where' => ['workflow_template_id in (?)'], 'data' => [$workflowTemplates]]);
            WorkflowTemplateModel::delete(['where' => ['owner = ?'], 'data'  => [$args['id']]]);
        }

        SignatureModel::delete(['where' => ['user_id = ?'], 'data' => [$args['id']]]);
        UserGroupModel::delete(['where' => ['user_id = ?'], 'data' => [$args['id']]]);
        WorkflowTemplateItemModel::delete(['where' => ['user_id = ?'], 'data' => [$args['id']]]);
Damien's avatar
Damien committed
418
419
420
        UserModel::delete(['id' => $args['id']]);

        HistoryController::add([
421
422
423
424
425
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $args['id'],
            'type'       => 'SUPPRESSION',
            'message'    => "{userDeleted} : {$user['firstname']} {$user['lastname']}"
Damien's avatar
Damien committed
426
427
428
429
430
        ]);

        return $response->withStatus(204);
    }

Damien's avatar
Damien committed
431
432
    public function getPictureById(Request $request, Response $response, array $args)
    {
433
434
435
436
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

Damien's avatar
Damien committed
437
438
439
440
441
442
443
444
        $user = UserModel::getById(['select' => ['picture'], 'id' => $args['id']]);
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

        return $response->withJson(['picture' => $user['picture']]);
    }

Damien's avatar
Damien committed
445
446
447
448
449
450
    public function getSubstituteById(Request $request, Response $response, array $args)
    {
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

451
452
453
454
        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users']) && $GLOBALS['id'] != $args['id']) {
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

Damien's avatar
Damien committed
455
456
457
458
459
460
461
462
        $user = UserModel::getById(['select' => ['substitute'], 'id' => $args['id']]);
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

        return $response->withJson(['substitute' => $user['substitute']]);
    }

463
464
465
466
467
468
    public function updatePreferences(Request $request, Response $response, array $args)
    {
        if ($GLOBALS['id'] != $args['id']) {
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

469
470
        $notificationsIds = NotificationsScheduleModel::get(['select' => ['id']]);
        $notificationsIds = array_column($notificationsIds, 'id');
471

472
473
        $body = $request->getParsedBody();

474
475
476
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        } elseif (!Validator::stringType()->notEmpty()->validate($body['lang'])) {
477
478
479
480
481
482
483
            return $response->withStatus(400)->withJson(['errors' => 'Body lang is empty or not a string']);
        } elseif (!Validator::stringType()->notEmpty()->validate($body['writingMode'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body writingMode is empty or not a string']);
        } elseif (!Validator::intType()->notEmpty()->validate($body['writingSize'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body writingSize is empty or not an integer']);
        } elseif (!Validator::stringType()->notEmpty()->validate($body['writingColor'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body writingColor is empty or not a string']);
484
485
        } elseif (!Validator::arrayType()->notEmpty()->validate($body['notifications'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body notifications is empty or not an array']);
486
        } elseif (!isset($body['notifications']['instant']) || !Validator::boolType()->validate($body['notifications']['instant'])) {
487
            return $response->withStatus(400)->withJson(['errors' => 'Body notifications.instant is not a boolean']);
488
        } elseif (!isset($body['notifications']['summaries']) || !Validator::arrayType()->each(Validator::in($notificationsIds))->validate($body['notifications']['summaries'])) {
489
            return $response->withStatus(400)->withJson(['errors' => 'Body notifications.summaries is not an array or contains invalid IDs']);
490
        } elseif (!isset($body['signatureScaling']) || !Validator::oneOf(Validator::falseVal(), Validator::intVal()->between(10, 50))->validate($body['signatureScaling'])) {
491
            return $response->withStatus(400)->withJson(['errors' => 'Body signatureScaling is neither false nor an integer between 10 and 50']);
492
493
        }

Damien's avatar
Damien committed
494
495
496
497
498
        $user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

499
        $body['notifications']['summaries'] = array_values(array_unique($body['notifications']['summaries']));
500
        $preferences = json_encode([
501
502
503
504
505
506
507
508
509
            'lang'             => $body['lang'],
            'writingMode'      => $body['writingMode'],
            'writingSize'      => $body['writingSize'],
            'writingColor'     => $body['writingColor'],
            'signatureScaling' => $body['signatureScaling'],
            'notifications'    => [
                'instant'      => $body['notifications']['instant'],
                'summaries'    => $body['notifications']['summaries'],
            ]
510
511
512
513
514
515
516
517
518
519
520
521
        ]);
        if (!is_string($preferences)) {
            return $response->withStatus(400)->withJson(['errors' => 'Wrong format for user preferences data']);
        }

        UserModel::update([
            'set'   => ['preferences' => $preferences],
            'where' => ['id = ?'],
            'data'  => [$args['id']]
        ]);

        HistoryController::add([
522
523
524
525
526
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $args['id'],
            'type'       => 'MODIFICATION',
            'message'    => "{userUpdated} : {$user['firstname']} {$user['lastname']}"
527
528
529
530
531
        ]);

        return $response->withStatus(204);
    }

532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
    public function updateSubstitute(Request $request, Response $response, array $args)
    {
        if ($GLOBALS['id'] != $args['id']) {
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

        $body = $request->getParsedBody();

        $user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

        $set = [
            'substitute' => null
        ];

        if (!empty($body['substitute']) && $args['id'] != $body['substitute']) {
            $existingUser = UserModel::getById(['id' => $body['substitute'], 'select' => ['substitute']]);
            if (empty($existingUser)) {
                return $response->withStatus(400)->withJson(['errors' => 'Substitute user does not exist']);
            } elseif (!empty($existingUser['substitute'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Substitute user has already substituted']);
            }

            $substitutedUsers = UserModel::get(['select' => ['id'], 'where' => ['substitute = ?'], 'data' => [$args['id']]]);
            foreach ($substitutedUsers as $user) {
                UserModel::update([
                    'set'   => ['substitute' => $body['substitute']],
                    'where' => ['id = ?'],
                    'data'  => [$user['id']]
                ]);
            }
            $set['substitute'] = $body['substitute'];
        }

        UserModel::update([
            'set'   => $set,
            'where' => ['id = ?'],
            'data'  => [$args['id']]
        ]);

        HistoryController::add([
575
576
577
578
579
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $args['id'],
            'type'       => 'MODIFICATION',
            'message'    => "{userUpdated} : {$user['firstname']} {$user['lastname']}"
580
581
582
583
584
        ]);

        return $response->withStatus(204);
    }

Damien's avatar
Damien committed
585
586
    public function updatePassword(Request $request, Response $response, array $args)
    {
587
588
589
590
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

591
592
593
594
595
596
        $user = UserModel::getById(['select' => ['login', '"isRest"'], 'id' => $args['id']]);
        $connection = ConfigurationModel::getConnection();
        if ($connection != 'default' && $user['isRest'] == false) {
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

Damien's avatar
Damien committed
597
598
599
600
601
602
        if ($GLOBALS['id'] != $args['id']) {
            if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
            }
        }

603
604
        $body = $request->getParsedBody();
        if (!Validator::stringType()->notEmpty()->validate($body['newPassword'])) {
Damien's avatar
Damien committed
605
            return $response->withStatus(400)->withJson(['errors' => 'Body newPassword is empty or not a string']);
606
        } elseif ($body['newPassword'] != $body['passwordConfirmation']) {
Damien's avatar
Damien committed
607
            return $response->withStatus(400)->withJson(['errors' => 'Body newPassword and passwordConfirmation must be identical']);
Damien's avatar
Damien committed
608
609
        }

Damien's avatar
Damien committed
610
        if ($user['isRest'] == false) {
611
            if (empty($body['currentPassword']) || !AuthenticationModel::authentication(['login' => $user['login'], 'password' => $body['currentPassword']])) {
612
                return $response->withStatus(401)->withJson(['errors' => 'Wrong Password', 'lang' => 'wrongCurrentPassword']);
Damien's avatar
Damien committed
613
614
            }
        }
615
        if (!PasswordController::isPasswordValid(['password' => $body['newPassword']])) {
Damien's avatar
Damien committed
616
            return $response->withStatus(400)->withJson(['errors' => 'Password does not match security criteria']);
617
        } elseif (!PasswordModel::isPasswordHistoryValid(['password' => $body['newPassword'], 'userId' => $args['id']])) {
618
            return $response->withStatus(400)->withJson(['errors' => 'Password has already been used', 'lang' => 'alreadyUsedPassword']);
Damien's avatar
Damien committed
619
620
        }

621
        UserModel::updatePassword(['id' => $args['id'], 'password' => $body['newPassword']]);
622
        PasswordModel::setHistoryPassword(['userId' => $args['id'], 'password' => $body['newPassword']]);
Damien's avatar
Damien committed
623

Damien's avatar
Damien committed
624
625
        $refreshToken = [];
        if ($GLOBALS['id'] == $args['id']) {
626
            $refreshJWT     = AuthenticationController::getRefreshJWT();
Damien's avatar
Damien committed
627
            $refreshToken[] = $refreshJWT;
628
629
            $response       = $response->withHeader('Token', AuthenticationController::getJWT());
            $response       = $response->withHeader('Refresh-Token', $refreshJWT);
Damien's avatar
Damien committed
630
631
632
633
634
635
636
637
        }

        UserModel::update([
            'set'   => ['refresh_token' => json_encode($refreshToken)],
            'where' => ['id = ?'],
            'data'  => [$args['id']]
        ]);

Damien's avatar
Damien committed
638
        HistoryController::add([
639
640
641
642
643
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $args['id'],
            'type'       => 'MODIFICATION',
            'message'    => '{userPasswordUpdated}'
Damien's avatar
Damien committed
644
645
        ]);

646
        return $response->withStatus(204);
Damien's avatar
Damien committed
647
648
    }

Damien's avatar
Damien committed
649
650
651
652
653
654
655
656
    public function forgotPassword(Request $request, Response $response)
    {
        $body = $request->getParsedBody();

        if (!Validator::stringType()->notEmpty()->validate($body['login'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Bad request']);
        }

657
        $user = UserModel::getByLogin(['select' => ['id', 'email', 'preferences'], 'login' => strtolower($body['login'])]);
Damien's avatar
Damien committed
658
        if (empty($user)) {
659
            return $response->withStatus(204);
Damien's avatar
Damien committed
660
661
662
663
        }

        $GLOBALS['id'] = $user['id'];

664
        $resetToken = AuthenticationController::getResetJWT(['id' => $GLOBALS['id'], 'expirationTime' => 3600]);
Damien's avatar
Damien committed
665
        UserModel::update(['set' => ['reset_token' => $resetToken], 'where' => ['id = ?'], 'data' => [$user['id']]]);
Damien's avatar
Damien committed
666
667

        $user['preferences'] = json_decode($user['preferences'], true);
Damien's avatar
Damien committed
668
        $lang = LanguageController::get(['lang' => $user['preferences']['lang']]);
Damien's avatar
Damien committed
669

670
        $url = ConfigurationModel::getApplicationUrl() . 'dist/update-password?token=' . $resetToken;
Damien's avatar
Damien committed
671
        EmailController::createEmail([
672
673
674
675
676
            'userId' => $user['id'],
            'data'   => [
                'sender'     => 'Notification',
                'recipients' => [$user['email']],
                'subject'    => $lang['notificationForgotPasswordSubject'],
677
                'body'       => $lang['notificationForgotPasswordBody'] . '<a href="' . $url . '">'.$url.'</a>' . $lang['notificationForgotPasswordFooter'],
678
                'isHtml'     => true
Damien's avatar
Damien committed
679
680
681
682
            ]
        ]);

        HistoryController::add([
683
684
685
686
687
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $user['id'],
            'type'       => 'MODIFICATION',
            'message'    => '{userPasswordForgotten}'
Damien's avatar
Damien committed
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
        ]);

        return $response->withStatus(204);
    }

    public static function updateForgottenPassword(Request $request, Response $response)
    {
        $body = $request->getParsedBody();

        $check = Validator::stringType()->notEmpty()->validate($body['token']);
        $check = $check && Validator::stringType()->notEmpty()->validate($body['password']);
        if (!$check) {
            return $response->withStatus(400)->withJson(['errors' => 'Bad Request']);
        }

Damien's avatar
Damien committed
703
704
705
        try {
            $jwt = JWT::decode($body['token'], CoreConfigModel::getEncryptKey(), ['HS256']);
        } catch (\Exception $e) {
706
            return $response->withStatus(403)->withJson(['errors' => 'Invalid token', 'lang' => 'invalidToken']);
Damien's avatar
Damien committed
707
        }
Damien's avatar
Damien committed
708

Damien's avatar
Damien committed
709
        $user = UserModel::getById(['id' => $jwt->user->id, 'select' => ['id', 'reset_token']]);
Damien's avatar
Damien committed
710
711
712
713
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

Damien's avatar
Damien committed
714
        if ($body['token'] != $user['reset_token']) {
715
            return $response->withStatus(403)->withJson(['errors' => 'Invalid token', 'lang' => 'invalidToken']);
Damien's avatar
Damien committed
716
717
718
719
720
721
722
723
        }

        if (!PasswordController::isPasswordValid(['password' => $body['password']])) {
            return $response->withStatus(400)->withJson(['errors' => 'Password does not match security criteria']);
        }

        UserModel::update([
            'set' => [
724
725
726
727
                'password'                   => AuthenticationModel::getPasswordHash($body['password']),
                'password_modification_date' => 'CURRENT_TIMESTAMP',
                'reset_token'                => null,
                'refresh_token'              => '[]'
Damien's avatar
Damien committed
728
729
            ],
            'where' => ['id = ?'],
Damien's avatar
Damien committed
730
            'data'  => [$user['id']]
Damien's avatar
Damien committed
731
732
733
734
        ]);

        $GLOBALS['id'] = $user['id'];
        HistoryController::add([
735
736
737
738
739
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $user['id'],
            'type'       => 'MODIFICATION',
            'message'    => '{userForgottenPasswordUpdated}'
Damien's avatar
Damien committed
740
741
742
743
744
        ]);

        return $response->withStatus(204);
    }

745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
    public function sendAccountActivationNotification(Request $request, Response $response, array $args)
    {
        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

        $connection = ConfigurationModel::getConnection();
        if ($connection != 'default') {
            return $response->withStatus(403)->withJson(['errors' => 'Cannot send activation notification when not using default connection']);
        }

        $user = UserModel::getById(['id' => $args['id'], 'select' => ['email']]);

        AuthenticationController::sendAccountActivationNotification(['userId' => $args['id'], 'userEmail' => $user['email']]);

        return $response->withStatus(204);
    }

767
768
769
770
771
772
773
774
775
776
777
    public function getManageableGroupsREST(Request $request, Response $response)
    {
        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

        $manageableGroups = UserController::getManageableGroups(['userId' => $GLOBALS['id']]);

        return $response->withStatus(200)->withJson(['groups' => $manageableGroups]);
    }

Damien's avatar
Damien committed
778
779
780
781
782
    public static function getUserInformationsById(array $args)
    {
        ValidatorModel::notEmpty($args, ['id']);
        ValidatorModel::intVal($args, ['id']);

Damien's avatar
Damien committed
783
        $user = UserModel::getById(['select' => ['id', 'login', 'email', 'firstname', 'lastname', 'phone', 'picture', 'preferences', 'substitute', '"isRest"', 'signature_modes', 'x509_fingerprint'], 'id' => $args['id']]);
784
785
786
        if (empty($user)) {
            return [];
        }
787
788
        $user['signatureModes'] = json_decode($user['signature_modes'], true);
        unset($user['signature_modes']);
Damien's avatar
Damien committed
789

790
791
792
793
        $validSignatureModes = CoreConfigModel::getSignatureModes();
        $validSignatureModes = array_column($validSignatureModes, 'id');
        $user['signatureModes'] = array_reverse(array_values(array_intersect($validSignatureModes, $user['signatureModes'])));

794
        if (empty($user['picture'])) {
Damien's avatar
Damien committed
795
796
797
798
            $user['picture'] = base64_encode(file_get_contents('src/frontend/assets/user_picture.png'));
            $user['picture'] = 'data:image/png;base64,' . $user['picture'];
        }

Damien's avatar
Damien committed
799
        if ($GLOBALS['id'] == $args['id']) {
800
801
            $user['preferences']                = json_decode($user['preferences'], true);
            $user['availableLanguages']         = LanguageController::getAvailableLanguages();
802
            $user['administrativePrivileges']   = PrivilegeController::getPrivilegesByUserId(['userId' => $args['id'], 'type' => 'admin']);
803
            $user['appPrivileges']              = PrivilegeController::getPrivilegesByUserId(['userId' => $args['id'], 'type' => 'simple']);
804
            if (!empty($user['substitute'])) {
Damien's avatar
Damien committed
805
                $user['substituteUser'] = UserModel::getLabelledUserById(['id' => $user['substitute']]);
806
            }
Damien's avatar
Damien committed
807
        }
Damien's avatar
Damien committed
808

809
810
811
812
813
814
        $currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);
        if ($currentUser['isRest']) {
            $user['x509Fingerprint'] = $user['x509_fingerprint'];
        }
        unset($user['x509_fingerprint']);

815
816
817
        $connection = ConfigurationModel::getConnection();
        $user['canSendActivationNotification'] = !$user['isRest'] && $connection == 'default';

Damien's avatar
Damien committed
818
819
        return $user;
    }
820
821
822
823
824
825
826
827
828

    public static function getManageableGroups(array $args)
    {
        ValidatorModel::notEmpty($args, ['userId']);
        ValidatorModel::intVal($args, ['userId']);

        if (PrivilegeController::hasPrivilege(['userId' => $args['userId'], 'privilege' => 'manage_groups'])) {
            $groups = GroupModel::get(['select' => ['id']]);
            $groups = array_column($groups, 'id');
829
            return GroupModel::get();
830
831
832
833
834
835
        }

        $groups = UserGroupModel::get(['select' => ['group_id'], 'where' => ['user_id = ?'], 'data' => [$args['userId']]]);

        $manageableGroups = [];
        foreach ($groups as $group) {
836
837
838
839
840
            $privilege = GroupPrivilegeModel::getPrivileges([
                'select' => ['parameters'],
                'where'  =>  ['group_id = ?', 'privilege = ?'],
                'data'   => [$group['group_id'], 'manage_users']
            ]);
841
842
843
844
            $parameters = empty($privilege[0]['parameters']) ? [] : json_decode($privilege[0]['parameters'], true);
            $currentGroups = $parameters['authorized'] ?? [];
            $manageableGroups = array_merge($manageableGroups, $currentGroups);
        }
845
        $manageableGroups = array_unique($manageableGroups);
846

847
848
849
850
        if (empty($manageableGroups)) {
            return [];
        }

851
852
853
854
855
856
        $manageableGroups = GroupModel::get([
            'where' => ['id in (?)'],
            'data'  => [$manageableGroups]
        ]);

        return $manageableGroups;
857
    }
Florian Azizian's avatar
Florian Azizian committed
858
}