UserController.php 34.3 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;
Damien's avatar
Damien committed
24
use History\controllers\HistoryController;
Florian Azizian's avatar
Florian Azizian committed
25
26
27
use Respect\Validation\Validator;
use Slim\Http\Request;
use Slim\Http\Response;
Damien's avatar
Damien committed
28
use SrcCore\controllers\AuthenticationController;
Damien's avatar
Damien committed
29
use SrcCore\controllers\LanguageController;
Damien's avatar
Damien committed
30
use SrcCore\controllers\PasswordController;
Damien's avatar
Damien committed
31
use SrcCore\controllers\UrlController;
Damien's avatar
Damien committed
32
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;
Florian Azizian's avatar
Florian Azizian committed
42
43
44

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

49
        $select = ['id', 'firstname', 'lastname', 'substitute', 'x509_fingerprint'];
Damien's avatar
Damien committed
50
51
52
53
54
        $where = [];
        $queryData = [];
        if (empty($queryParams['mode'])) {
            $where = ['"isRest" = ?'];
            $queryData = ['false'];
55
56
57
58
        }
        if (PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
            $select[] = 'login';
            $select[] = 'email';
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
        if ($GLOBALS['id'] != $args['id'] && !PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
84
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
85
86
        }

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

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

96
97
98
        $user['groups'] = [];

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

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

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

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

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

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

138
        $body['login'] = strtolower($body['login']);
Damien's avatar
Damien committed
139
        $existingUser = UserModel::getByLogin(['login' => $body['login'], 'select' => [1]]);
Florian Azizian's avatar
Florian Azizian committed
140
        if (!empty($existingUser)) {
141
            return $response->withStatus(400)->withJson(['errors' => 'Login already exists', 'lang' => 'userLoginAlreadyExists']);
Florian Azizian's avatar
Florian Azizian committed
142
        }
143
144
        
        $body['x509_fingerprint'] = $body['x509Fingerprint'];
Florian Azizian's avatar
Florian Azizian committed
145

Damien's avatar
Damien committed
146
        if (!empty($body['isRest'])) {
147
            $body['"isRest"'] = true;
Florian Azizian's avatar
Florian Azizian committed
148
        }
149
150
151
152
        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
153

154
155
156
157
        if (!empty($body['signatureModes'])) {
            if (!Validator::arrayType()->validate($body['signatureModes'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Body signatureModes is not an array']);
            } else {
158
                $body['signatureModes'] = array_unique($body['signatureModes']);
159
160
161
                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"]);
162
163
164
165
                    }
                }
            }
        } else {
166
            $body['signatureModes'] = ['stamp'];
167
168
169
        }
        $body['signatureModes'] = json_encode($body['signatureModes']);

Damien's avatar
Damien committed
170
        $id = UserModel::create($body);
Florian Azizian's avatar
Florian Azizian committed
171
172

        HistoryController::add([
Damien's avatar
Damien committed
173
174
175
176
            'code'          => 'OK',
            'objectType'    => 'users',
            'objectId'      => $id,
            'type'          => 'CREATION',
Damien's avatar
Damien committed
177
            'message'       => "{userAdded} : {$body['firstname']} {$body['lastname']}"
Florian Azizian's avatar
Florian Azizian committed
178
179
        ]);

180
181
182
183
        if (empty($body['isRest'])) {
            AuthenticationController::sendAccountActivationNotification(['userId' => $id, 'userEmail' => $body['email']]);
        }

Damien's avatar
Damien committed
184
        return $response->withJson(['id' => $id]);
Florian Azizian's avatar
Florian Azizian committed
185
186
    }

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

Damien's avatar
Damien committed
194
        $body = $request->getParsedBody();
195

196
197
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
198
        } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['firstname'])) {
199
            return $response->withStatus(400)->withJson(['errors' => 'Body firstname is empty or not a string']);
200
        } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['lastname'])) {
201
            return $response->withStatus(400)->withJson(['errors' => 'Body lastname is empty or not a string']);
202
        } 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
203
            return $response->withStatus(400)->withJson(['errors' => 'Body email is empty or not a valid email']);
204
205
        } 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
206
207
        }

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

213
        $set = [
214
215
216
217
            'firstname'       => $body['firstname'],
            'lastname'        => $body['lastname'],
            'email'           => $body['email'],
            'signature_modes' => []
218
219
        ];

220
221
222
223
224
        $currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);
        if ($currentUser['isRest']) {
            $set['x509_fingerprint'] = $body['x509Fingerprint'];
        }

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

245
            WorkflowModel::update([
246
                'set'   => ['signature_mode' => $higherMode],
247
                'where' => ['user_id = ?', 'signature_mode not in (?)', 'process_date is null', 'signature_mode != ?'],
248
                'data'  => [$args['id'], $modes, $higherMode]
249
250
            ]);
            WorkflowTemplateItemModel::update([
251
                'set'   => ['signature_mode' => $higherMode],
252
                'where' => ['user_id = ?', 'signature_mode not in (?)', 'signature_mode != ?'],
253
                'data'  => [$args['id'], $modes, $higherMode]
254
255
            ]);

256
            $set['signature_modes'] = $modes;
257
258
259
        }
        $set['signature_modes'] = json_encode($set['signature_modes']);

Damien's avatar
Damien committed
260
261
262
263
264
        UserModel::update([
            'set'   => $set,
            'where' => ['id = ?'],
            'data'  => [$args['id']]
        ]);
Damien's avatar
Damien committed
265

Damien's avatar
Damien committed
266
        HistoryController::add([
Damien's avatar
Damien committed
267
268
269
270
            'code'          => 'OK',
            'objectType'    => 'users',
            'objectId'      => $args['id'],
            'type'          => 'MODIFICATION',
Damien's avatar
Damien committed
271
            'message'       => "{userUpdated} : {$body['firstname']} {$body['lastname']}"
Damien's avatar
Damien committed
272
273
        ]);

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

277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
    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);
        }
299
300
301
302
        $picture  = base64_decode($body['picture']);
        $finfo    = new \finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->buffer($picture);
        $type     = explode('/', $mimeType);
303
304
305
306
307

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

308
309
        $imagick = new \Imagick();
        $imagick->readImageBlob(base64_decode($body['picture']));
310
311
312
        if (!empty($body['pictureOrientation'])) {
            $imagick->rotateImage(new \ImagickPixel(), $body['pictureOrientation']);
        }
313
        $imagick->thumbnailImage(100, null);
314
        $body['picture'] = base64_encode($imagick->getImagesBlob());
315
316

        $set = [
317
            'picture' => $infoContent . $body['picture']
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
        ];

        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']}"
        ]);

334
        return $response->withStatus(204);
335
336
    }

Damien's avatar
Damien committed
337
338
    public function delete(Request $request, Response $response, array $args)
    {
339
        if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users']) || $GLOBALS['id'] == $args['id']) {
Damien's avatar
Damien committed
340
341
342
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

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

Damien's avatar
Damien committed
347
348
349
350
351
        $user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

352
353
354
        $substitutedUsers = UserModel::get(['select' => ['id'], 'where' => ['substitute = ?'], 'data' => [$args['id']]]);
        $allSubstitutedUsers = array_column($substitutedUsers, 'id');

Damien's avatar
Damien committed
355
        $workflows = WorkflowModel::get([
356
357
            'select' => ['main_document_id'],
            'where'  => ['user_id = ?', "process_date IS NULL", "status IS NULL"],
358
359
360
            'data'   => [$args['id']]
        ]);

361
        $mainDocumentId = array_column($workflows, 'main_document_id');
Damien's avatar
Damien committed
362

363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
        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]
            ]);
            
            $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]
            ]);
    
            $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']) {
                    EmailController::sendNotificationToTypist(['documentId' => $document['id'], 'senderId' => $GLOBALS['id'], 'recipientId' => $document['typist'], 'mode' => 'DEL']);
                }
                $previousDocumentId = $step['main_document_id'];
388
389
            }
        }
Damien's avatar
Damien committed
390
391
392
393
394
395

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

396
397
398
399
400
        $workflowTemplates = WorkflowTemplateModel::get([
            'select' => ['id'],
            'where'  => ['owner = ?'],
            'data'   => [$args['id']]
        ]);
Damien's avatar
Damien committed
401

402
403
404
405
406
407
408
409
410
        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
411
412
413
        UserModel::delete(['id' => $args['id']]);

        HistoryController::add([
414
415
416
417
418
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $args['id'],
            'type'       => 'SUPPRESSION',
            'message'    => "{userDeleted} : {$user['firstname']} {$user['lastname']}"
Damien's avatar
Damien committed
419
420
421
422
423
        ]);

        return $response->withStatus(204);
    }

Damien's avatar
Damien committed
424
425
    public function getPictureById(Request $request, Response $response, array $args)
    {
426
427
428
429
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

Damien's avatar
Damien committed
430
431
432
433
434
435
436
437
        $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
438
439
440
441
442
443
    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']);
        }

444
445
446
447
        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
448
449
450
451
452
453
454
455
        $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']]);
    }

456
457
458
459
460
461
462
463
    public function updatePreferences(Request $request, Response $response, array $args)
    {
        if ($GLOBALS['id'] != $args['id']) {
            return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
        }

        $body = $request->getParsedBody();

464
465
466
        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'])) {
467
468
469
470
471
472
473
474
475
476
477
            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']);
        } elseif (!Validator::boolType()->validate($body['notifications'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Body notifications is empty or not a boolean']);
        }

Damien's avatar
Damien committed
478
479
480
481
482
        $user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
        $preferences = json_encode([
            'lang'          => $body['lang'],
            'writingMode'   => $body['writingMode'],
            'writingSize'   => $body['writingSize'],
            'writingColor'  => $body['writingColor'],
            'notifications' => $body['notifications'],
        ]);
        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([
501
502
503
504
505
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $args['id'],
            'type'       => 'MODIFICATION',
            'message'    => "{userUpdated} : {$user['firstname']} {$user['lastname']}"
506
507
508
509
510
        ]);

        return $response->withStatus(204);
    }

511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
    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([
554
555
556
557
558
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $args['id'],
            'type'       => 'MODIFICATION',
            'message'    => "{userUpdated} : {$user['firstname']} {$user['lastname']}"
559
560
561
562
563
        ]);

        return $response->withStatus(204);
    }

Damien's avatar
Damien committed
564
565
    public function updatePassword(Request $request, Response $response, array $args)
    {
566
567
568
569
        if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
            return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
        }

570
571
572
573
574
575
        $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
576
577
578
579
580
581
        if ($GLOBALS['id'] != $args['id']) {
            if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
            }
        }

582
583
        $body = $request->getParsedBody();
        if (!Validator::stringType()->notEmpty()->validate($body['newPassword'])) {
Damien's avatar
Damien committed
584
            return $response->withStatus(400)->withJson(['errors' => 'Body newPassword is empty or not a string']);
585
        } elseif ($body['newPassword'] != $body['passwordConfirmation']) {
Damien's avatar
Damien committed
586
            return $response->withStatus(400)->withJson(['errors' => 'Body newPassword and passwordConfirmation must be identical']);
Damien's avatar
Damien committed
587
588
        }

Damien's avatar
Damien committed
589
        if ($user['isRest'] == false) {
590
            if (empty($body['currentPassword']) || !AuthenticationModel::authentication(['login' => $user['login'], 'password' => $body['currentPassword']])) {
591
                return $response->withStatus(401)->withJson(['errors' => 'Wrong Password', 'lang' => 'wrongCurrentPassword']);
Damien's avatar
Damien committed
592
593
            }
        }
594
        if (!PasswordController::isPasswordValid(['password' => $body['newPassword']])) {
Damien's avatar
Damien committed
595
            return $response->withStatus(400)->withJson(['errors' => 'Password does not match security criteria']);
596
        } elseif (!PasswordModel::isPasswordHistoryValid(['password' => $body['newPassword'], 'userId' => $args['id']])) {
597
            return $response->withStatus(400)->withJson(['errors' => 'Password has already been used', 'lang' => 'alreadyUsedPassword']);
Damien's avatar
Damien committed
598
599
        }

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

Damien's avatar
Damien committed
603
604
        $refreshToken = [];
        if ($GLOBALS['id'] == $args['id']) {
605
            $refreshJWT     = AuthenticationController::getRefreshJWT();
Damien's avatar
Damien committed
606
            $refreshToken[] = $refreshJWT;
607
608
            $response       = $response->withHeader('Token', AuthenticationController::getJWT());
            $response       = $response->withHeader('Refresh-Token', $refreshJWT);
Damien's avatar
Damien committed
609
610
611
612
613
614
615
616
        }

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

Damien's avatar
Damien committed
617
        HistoryController::add([
618
619
620
621
622
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $args['id'],
            'type'       => 'MODIFICATION',
            'message'    => '{userPasswordUpdated}'
Damien's avatar
Damien committed
623
624
        ]);

625
        return $response->withStatus(204);
Damien's avatar
Damien committed
626
627
    }

Damien's avatar
Damien committed
628
629
630
631
632
633
634
635
    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']);
        }

636
        $user = UserModel::getByLogin(['select' => ['id', 'email', 'preferences'], 'login' => strtolower($body['login'])]);
Damien's avatar
Damien committed
637
        if (empty($user)) {
638
            return $response->withStatus(204);
Damien's avatar
Damien committed
639
640
641
642
        }

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

643
        $resetToken = AuthenticationController::getResetJWT(['id' => $GLOBALS['id'], 'expirationTime' => 3600]);
Damien's avatar
Damien committed
644
        UserModel::update(['set' => ['reset_token' => $resetToken], 'where' => ['id = ?'], 'data' => [$user['id']]]);
Damien's avatar
Damien committed
645
646

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

649
        $url = UrlController::getCoreUrl() . 'dist/update-password?token=' . $resetToken;
Damien's avatar
Damien committed
650
        EmailController::createEmail([
651
652
653
654
655
            'userId' => $user['id'],
            'data'   => [
                'sender'     => 'Notification',
                'recipients' => [$user['email']],
                'subject'    => $lang['notificationForgotPasswordSubject'],
656
                'body'       => $lang['notificationForgotPasswordBody'] . '<a href="' . $url . '">'.$url.'</a>' . $lang['notificationForgotPasswordFooter'],
657
                'isHtml'     => true
Damien's avatar
Damien committed
658
659
660
661
            ]
        ]);

        HistoryController::add([
662
663
664
665
666
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $user['id'],
            'type'       => 'MODIFICATION',
            'message'    => '{userPasswordForgotten}'
Damien's avatar
Damien committed
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
        ]);

        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
682
683
684
        try {
            $jwt = JWT::decode($body['token'], CoreConfigModel::getEncryptKey(), ['HS256']);
        } catch (\Exception $e) {
685
            return $response->withStatus(403)->withJson(['errors' => 'Invalid token', 'lang' => 'invalidToken']);
Damien's avatar
Damien committed
686
        }
Damien's avatar
Damien committed
687

Damien's avatar
Damien committed
688
        $user = UserModel::getById(['id' => $jwt->user->id, 'select' => ['id', 'reset_token']]);
Damien's avatar
Damien committed
689
690
691
692
        if (empty($user)) {
            return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
        }

Damien's avatar
Damien committed
693
        if ($body['token'] != $user['reset_token']) {
694
            return $response->withStatus(403)->withJson(['errors' => 'Invalid token', 'lang' => 'invalidToken']);
Damien's avatar
Damien committed
695
696
697
698
699
700
701
702
        }

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

        UserModel::update([
            'set' => [
703
704
705
706
                'password'                   => AuthenticationModel::getPasswordHash($body['password']),
                'password_modification_date' => 'CURRENT_TIMESTAMP',
                'reset_token'                => null,
                'refresh_token'              => '[]'
Damien's avatar
Damien committed
707
708
            ],
            'where' => ['id = ?'],
Damien's avatar
Damien committed
709
            'data'  => [$user['id']]
Damien's avatar
Damien committed
710
711
712
713
        ]);

        $GLOBALS['id'] = $user['id'];
        HistoryController::add([
714
715
716
717
718
            'code'       => 'OK',
            'objectType' => 'users',
            'objectId'   => $user['id'],
            'type'       => 'MODIFICATION',
            'message'    => '{userForgottenPasswordUpdated}'
Damien's avatar
Damien committed
719
720
721
722
723
        ]);

        return $response->withStatus(204);
    }

724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
    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);
    }

Damien's avatar
Damien committed
746
747
748
749
750
    public static function getUserInformationsById(array $args)
    {
        ValidatorModel::notEmpty($args, ['id']);
        ValidatorModel::intVal($args, ['id']);

751
        $user = UserModel::getById(['select' => ['id', 'login', 'email', 'firstname', 'lastname', 'picture', 'preferences', 'substitute', '"isRest"', 'signature_modes', 'x509_fingerprint'], 'id' => $args['id']]);
752
753
754
        if (empty($user)) {
            return [];
        }
755
756
        $user['signatureModes'] = json_decode($user['signature_modes'], true);
        unset($user['signature_modes']);
Damien's avatar
Damien committed
757

758
759
760
761
        $validSignatureModes = CoreConfigModel::getSignatureModes();
        $validSignatureModes = array_column($validSignatureModes, 'id');
        $user['signatureModes'] = array_reverse(array_values(array_intersect($validSignatureModes, $user['signatureModes'])));

762
        if (empty($user['picture'])) {
Damien's avatar
Damien committed
763
764
765
766
            $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
767
        if ($GLOBALS['id'] == $args['id']) {
768
769
            $user['preferences']                = json_decode($user['preferences'], true);
            $user['availableLanguages']         = LanguageController::getAvailableLanguages();
770
            $user['administrativePrivileges']   = PrivilegeController::getPrivilegesByUserId(['userId' => $args['id'], 'type' => 'admin']);
771
            $user['appPrivileges']              = PrivilegeController::getPrivilegesByUserId(['userId' => $args['id'], 'type' => 'simple']);
772
            if (!empty($user['substitute'])) {
Damien's avatar
Damien committed
773
                $user['substituteUser'] = UserModel::getLabelledUserById(['id' => $user['substitute']]);
774
            }
Damien's avatar
Damien committed
775
        }
Damien's avatar
Damien committed
776

777
778
779
780
781
782
        $currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);
        if ($currentUser['isRest']) {
            $user['x509Fingerprint'] = $user['x509_fingerprint'];
        }
        unset($user['x509_fingerprint']);

783
784
785
        $connection = ConfigurationModel::getConnection();
        $user['canSendActivationNotification'] = !$user['isRest'] && $connection == 'default';

Damien's avatar
Damien committed
786
787
        return $user;
    }
Florian Azizian's avatar
Florian Azizian committed
788
}