Skip to content
Snippets Groups Projects
UserController.php 42.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Florian Azizian's avatar
    Florian Azizian committed
    <?php
    
    /**
    
    * Copyright Maarch since 2008 under license.
    * See LICENSE.txt file at the root folder for more details.
    
    Florian Azizian's avatar
    Florian Azizian committed
    * This file is part of Maarch software.
    *
    */
    
    /**
    * @brief User Controller
    * @author dev@maarch.org
    */
    
    namespace User\controllers;
    
    
    use Document\controllers\DigitalSignatureController;
    use Document\models\DocumentModel;
    
    use Email\controllers\EmailController;
    
    use Firebase\JWT\JWT;
    
    use Group\controllers\PrivilegeController;
    
    use Group\models\GroupModel;
    
    use Group\models\GroupPrivilegeModel;
    
    Damien's avatar
    Damien committed
    use History\controllers\HistoryController;
    
    Florian Azizian's avatar
    Florian Azizian committed
    use Respect\Validation\Validator;
    
    use Slim\Psr7\Request;
    use SrcCore\http\Response;
    
    use SrcCore\controllers\AuthenticationController;
    
    Damien's avatar
    Damien committed
    use SrcCore\controllers\LanguageController;
    
    use SrcCore\controllers\PasswordController;
    use SrcCore\models\AuthenticationModel;
    
    use SrcCore\models\CoreConfigModel;
    
    use SrcCore\models\PasswordModel;
    
    use SrcCore\models\ValidatorModel;
    
    use User\models\SignatureModel;
    
    use User\models\UserGroupModel;
    
    Florian Azizian's avatar
    Florian Azizian committed
    use User\models\UserModel;
    
    use Workflow\models\WorkflowModel;
    
    use Workflow\models\WorkflowTemplateItemModel;
    
    use Workflow\models\WorkflowTemplateModel;
    
    use Notification\models\NotificationsScheduleModel;
    
    Florian Azizian's avatar
    Florian Azizian committed
    
    class UserController
    {
    
    Damien's avatar
    Damien committed
        public function get(Request $request, Response $response)
        {
    
    Damien's avatar
    Damien committed
            $queryParams = $request->getQueryParams();
    
    Damien's avatar
    Damien committed
            $select = ['id', 'firstname', 'lastname', 'email', 'phone', 'substitute', 'x509_fingerprint'];
    
            $where = [];
            $queryData = [];
            if (empty($queryParams['mode'])) {
                $where = ['"isRest" = ?'];
                $queryData = ['false'];
    
            }
            if (PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
                $select[] = 'login';
    
    Damien's avatar
    Damien committed
            $users = UserModel::get([
    
                'select'    => $select,
    
                'where'     => $where,
    
                'data'      => $queryData,
    
    Damien's avatar
    Damien committed
                'orderBy'   => ['lastname', 'firstname']
    
    Damien's avatar
    Damien committed
            ]);
    
    
            $currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);
    
            $manageableGroups = UserController::getManageableGroups(['userId' => $GLOBALS['id']]);
            $manageableGroups = array_column($manageableGroups, 'id');
    
            $allGroupLabels = GroupModel::get(['select' => ['label', 'id']]);
            $allUsersGroups = UserGroupModel::get(['select' => ['user_id', 'group_id']]);
    
            foreach ($users as $key => $user) {
                $users[$key]['substitute'] = !empty($user['substitute']);
    
                if ($currentUser['isRest']) {
                    $users[$key]['x509Fingerprint'] = $users[$key]['x509_fingerprint'];
                }
                unset($users[$key]['x509_fingerprint']);
    
                $groupsIds = array_column(array_filter($allUsersGroups, function ($userGroup) use ($user) {
                    return $userGroup['user_id'] == $user['id'];
                }), 'group_id');
    
                $actuallyAlone = false;
                if (empty($groupsIds)) {
                    $actuallyAlone = true;
                } elseif ($GLOBALS['id'] != $user['id']) {
    
                    $groupsIds = array_values(array_intersect($groupsIds, $manageableGroups));
                }
                if (!empty($groupsIds)) {
    
                    $users[$key]['groups'] = array_filter($allGroupLabels, function ($group) use ($groupsIds) {
                        return in_array($group['id'], $groupsIds);
                    });
    
            return $response->withJson(['users' => array_values($users)]);
    
    Damien's avatar
    Damien committed
        }
    
    
        public function getById(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']);
            }
    
    
            if ($GLOBALS['id'] == $args['id'] || PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
    
                $user = UserController::getUserInformationsById(['id' => $args['id']]);
    
                if (empty($user)) {
                    return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
                }
    
                $user['groups'] = [];
                $userGroups = UserGroupModel::get(['select' => ['group_id'], 'where' => ['user_id = ?'], 'data' => [$args['id']]]);
                $groupsIds  = array_column($userGroups, 'group_id');
                if ($GLOBALS['id'] != $args['id']) {
                    $groupsIds = array_values(array_intersect($groupsIds, array_column(UserController::getManageableGroups(['userId' => $GLOBALS['id']]), 'id')));
                }
    
                if(!empty($groupsIds)){
                    $groups = GroupModel::get(['select' => ['label', 'id'], 'where' => ['id in (?)'], 'data' => [$groupsIds]]);
                    $user['groups'] = $groups;
                }
    
                $user = UserModel::getById(['select' => ['id', 'firstname', 'lastname', 'email', 'phone', 'substitute'], 'id' => $args['id']]);
    
            if (empty($user)) {
                return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
    
            HistoryController::add([
                'code'          => 'OK',
                'objectType'    => 'users',
                'objectId'      => $args['id'],
                'type'          => 'VIEW',
                'message'       => "{userViewed} : {$user['firstname']} {$user['lastname']}"
            ]);
    
    
    Florian Azizian's avatar
    Florian Azizian committed
            return $response->withJson(['user' => $user]);
    
        public function create(Request $request, Response $response)
        {
    
            if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
    
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
    
    Damien's avatar
    Damien committed
            $body = $request->getParsedBody();
    
            if (empty($body)) {
                return $response->withStatus(400)->withJson(['errors' => 'Body is not set or empty']);
    
            } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['login']) || !preg_match("/^[\w.@-]*$/", $body['login'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body login is empty, not a string or wrong formatted']);
    
            } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['firstname'])) {
    
    Damien's avatar
    Damien committed
                return $response->withStatus(400)->withJson(['errors' => 'Body firstname is empty or not a string']);
    
            } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['lastname'])) {
    
    Damien's avatar
    Damien committed
                return $response->withStatus(400)->withJson(['errors' => 'Body lastname is empty or not a string']);
    
            } 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
                return $response->withStatus(400)->withJson(['errors' => 'Body email is empty or not a valid email']);
    
            } elseif (!empty($body['x509Fingerprint']) && !Validator::stringType()->validate($body['x509Fingerprint'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Body x509Fingerprint is not a string']);
    
            } elseif (!empty($body['groups']) && !Validator::arrayType()->validate($body['groups'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Body groups is not an array']);
    
            $body['groups'] = !empty($body['groups']) ? array_column($body['groups'], 'id') : [];
    
            $manageableGroups = array_column(UserController::getManageableGroups(['userId' => $GLOBALS['id']]), 'id');
            $body['groups'] = array_values(array_intersect($body['groups'], $manageableGroups));
    
            $body['login'] = strtolower($body['login']);
    
    Damien's avatar
    Damien committed
            $existingUser = UserModel::getByLogin(['login' => $body['login'], 'select' => [1]]);
    
            if (!empty($existingUser)) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Login already exists', 'lang' => 'userLoginAlreadyExists']);
    
            $body['x509_fingerprint'] = !empty($body['x509Fingerprint']) ? $body['x509Fingerprint'] : null;
    
            if (!empty($body['isRest'])) {
    
            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'];
            }
    
            if (!empty($body['signatureModes'])) {
                if (!Validator::arrayType()->validate($body['signatureModes'])) {
                    return $response->withStatus(400)->withJson(['errors' => 'Body signatureModes is not an array']);
                } else {
    
                    $body['signatureModes'] = array_unique($body['signatureModes']);
    
                    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"]);
    
            }
            $body['signatureModes'] = json_encode($body['signatureModes']);
    
    
    Damien's avatar
    Damien committed
            $id = UserModel::create($body);
    
    
            HistoryController::add([
    
    Damien's avatar
    Damien committed
                'code'          => 'OK',
                'objectType'    => 'users',
                'objectId'      => $id,
                'type'          => 'CREATION',
    
    Damien's avatar
    Damien committed
                'message'       => "{userAdded} : {$body['firstname']} {$body['lastname']}"
    
            if (empty($body['isRest'])) {
                AuthenticationController::sendAccountActivationNotification(['userId' => $id, 'userEmail' => $body['email']]);
            }
    
    
    Damien's avatar
    Damien committed
            return $response->withJson(['id' => $id]);
    
        public function update(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']);
            }
    
            $connection = ConfigurationModel::getConnection();
    
            if (($GLOBALS['id'] != $args['id'] || $connection != 'default') && !UserController::hasRightByUserId(['activeUserId' => $GLOBALS['id'], 'targetUserId' => $args['id']])) {
    
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
            }
    
    Damien's avatar
    Damien committed
            $body = $request->getParsedBody();
    
            if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
    
            } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['firstname'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body firstname is empty or not a string']);
    
            } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($body['lastname'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body lastname is empty or not a string']);
    
            } 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
                return $response->withStatus(400)->withJson(['errors' => 'Body email is empty or not a valid email']);
    
            } elseif (!empty($body['x509Fingerprint']) && !Validator::stringType()->validate($body['x509Fingerprint'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Body x509Fingerprint is not a string']);
    
            } elseif (!empty($body['groups']) && !Validator::arrayType()->each(Validator::intType())->validate($body['groups'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body groups is not an array of integers']);
    
            $user = UserModel::getById(['id' => $args['id'], 'select' => [1]]);
    
            if (empty($user)) {
                return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
            }
    
    
                'firstname'       => $body['firstname'],
                'lastname'        => $body['lastname'],
                'email'           => $body['email'],
    
            $currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);
            if ($currentUser['isRest']) {
                $set['x509_fingerprint'] = $body['x509Fingerprint'];
            }
    
    
            if (!empty($body['signatureModes'])) {
                if (!Validator::arrayType()->validate($body['signatureModes'])) {
    
                    return $response->withStatus(400)->withJson(['errors' => 'Body signatureModes is not an array']);
    
                $body['signatureModes'] = array_unique($body['signatureModes']);
    
                $modes = [];
                foreach ($body['signatureModes'] as $signatureMode) {
                    if (SignatureController::isValidSignatureMode(['mode' => $signatureMode])) {
                        $modes[] = $signatureMode;
    
                $validModes = CoreConfigModel::getSignatureModes();
                $higherMode = 'stamp';
                foreach ($validModes as $validMode) {
                    if (in_array($validMode['id'], $modes)) {
                        $higherMode = $validMode['id'];
                        break;
                    }
                }
    
    
                WorkflowModel::update([
    
                    'set'   => ['signature_mode' => $higherMode],
    
                    'where' => ['user_id = ?', 'signature_mode not in (?)', 'process_date is null', 'signature_mode != ?'],
    
                    'data'  => [$args['id'], $modes, $higherMode]
    
                ]);
                WorkflowTemplateItemModel::update([
    
                    'set'   => ['signature_mode' => $higherMode],
    
                    'where' => ['user_id = ?', 'signature_mode not in (?)', 'signature_mode != ?'],
    
                    'data'  => [$args['id'], $modes, $higherMode]
    
                $set['signature_modes'] = $modes;
    
            }
            $set['signature_modes'] = json_encode($set['signature_modes']);
    
    
    Damien's avatar
    Damien committed
            UserModel::update([
                'set'   => $set,
                'where' => ['id = ?'],
                'data'  => [$args['id']]
            ]);
    
            if (!empty($body['groups']) && Validator::arrayType()->each(Validator::intType())->validate($body['groups'])) {
                /**
                 * Alice wants to edit Bob’s groups.
                 *
                 * B: $body[groups]: groups Alice wants to apply to Bob
                 * M: $GLOBALS[id] manageable groups: groups Alice has right on
                 * C: current groups: Bob’s current groups
                 *
                 * given these, Bob’s new groups are the groups Alice asked for,
                 * plus “Bob’s current groups except groups Alice has right on”:
                 *
                 * B union (C - M)
                 */
                $targetCurrentGroups = UserGroupModel::get([
                    'select' => ['group_id'],
                    'where'  => ['user_id = ?'],
                    'data'   => [$args['id']]
                ]);
                $targetCurrentGroups = !empty($targetCurrentGroups) ? array_column($targetCurrentGroups, 'group_id') : [];
                $manageableGroups = array_column(UserController::getManageableGroups(['userId' => $GLOBALS['id']]), 'id');
                $appliedGroups = array_unique(array_merge($body['groups'], array_diff($targetCurrentGroups, $manageableGroups)));
                UserGroupModel::setUserGroups(['userId' => $args['id'], 'groups' => $appliedGroups]);
            }
    
    Damien's avatar
    Damien committed
            HistoryController::add([
    
    Damien's avatar
    Damien committed
                'code'          => 'OK',
                'objectType'    => 'users',
                'objectId'      => $args['id'],
                'type'          => 'MODIFICATION',
    
    Damien's avatar
    Damien committed
                'message'       => "{userUpdated} : {$body['firstname']} {$body['lastname']}"
    
    Damien's avatar
    Damien committed
            return $response->withJson(['user' => UserController::getUserInformationsById(['id' => $args['id']])]);
    
        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);
            }
    
            $picture  = base64_decode($body['picture']);
            $finfo    = new \finfo(FILEINFO_MIME_TYPE);
            $mimeType = $finfo->buffer($picture);
            $type     = explode('/', $mimeType);
    
    
            if ($type[0] != 'image') {
                return $response->withStatus(400)->withJson(['errors' => 'Picture is not an image']);
            }
    
    
            $imagick = new \Imagick();
            $imagick->readImageBlob(base64_decode($body['picture']));
    
            if (!empty($body['pictureOrientation'])) {
                $imagick->rotateImage(new \ImagickPixel(), $body['pictureOrientation']);
            }
    
            $body['picture'] = base64_encode($imagick->getImagesBlob());
    
                'picture' => $infoContent . $body['picture']
    
            ];
    
            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']}"
            ]);
    
    
            return $response->withStatus(204);
    
        public function delete(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']);
            }
    
    
            if ($GLOBALS['id'] == $args['id'] || !UserController::hasRightByUserId(['activeUserId' => $GLOBALS['id'], 'targetUserId' => $args['id']])) {
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
            }
    
    
            $user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
            if (empty($user)) {
                return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
            }
    
    
            $substitutedUsers = UserModel::get(['select' => ['id'], 'where' => ['substitute = ?'], 'data' => [$args['id']]]);
            $allSubstitutedUsers = array_column($substitutedUsers, 'id');
    
    
            $workflows = WorkflowModel::get([
    
                'select' => ['main_document_id'],
                'where'  => ['user_id = ?', "process_date IS NULL", "status IS NULL"],
    
            $mainDocumentId = array_column($workflows, 'main_document_id');
    
            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::sendOrFillNotificationToTypist(['documentId' => $document['id'], 'senderId' => $GLOBALS['id'], 'recipientId' => $document['typist'], 'mode' => 'DEL']);
    
                    }
                    $previousDocumentId = $step['main_document_id'];
    
    
            //Substituted Users
            if (!empty($allSubstitutedUsers)) {
                UserModel::update(['set' => ['substitute' => null], 'where' => ['id in (?)'], 'data' => [$allSubstitutedUsers]]);
            }
    
    
            $workflowTemplates = WorkflowTemplateModel::get([
                'select' => ['id'],
                'where'  => ['owner = ?'],
                'data'   => [$args['id']]
            ]);
    
            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']]]);
    
            UserModel::delete(['id' => $args['id']]);
    
            HistoryController::add([
    
                'code'       => 'OK',
                'objectType' => 'users',
                'objectId'   => $args['id'],
                'type'       => 'SUPPRESSION',
                'message'    => "{userDeleted} : {$user['firstname']} {$user['lastname']}"
    
            ]);
    
            return $response->withStatus(204);
        }
    
    
        public function getPictureById(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']);
            }
    
    
            $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']]);
        }
    
    
        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']);
            }
    
    
            if ($GLOBALS['id'] != $args['id'] && !UserController::hasRightByUserId(['activeUserId' => $GLOBALS['id'], 'targetUserId' => $args['id']])) {
    
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
            }
    
            $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']]);
        }
    
    
        public function updatePreferences(Request $request, Response $response, array $args)
        {
            if ($GLOBALS['id'] != $args['id']) {
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
            }
    
    
            $notificationsIds = NotificationsScheduleModel::get(['select' => ['id']]);
            $notificationsIds = array_column($notificationsIds, 'id');
    
            $body = $request->getParsedBody();
    
    
            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'])) {
    
                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::arrayType()->notEmpty()->validate($body['notifications'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Body notifications is empty or not an array']);
    
            } elseif (!isset($body['notifications']['instant']) || !Validator::boolType()->validate($body['notifications']['instant'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body notifications.instant is not a boolean']);
    
            } elseif (!isset($body['notifications']['summaries']) || !Validator::arrayType()->each(Validator::in($notificationsIds))->validate($body['notifications']['summaries'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body notifications.summaries is not an array or contains invalid IDs']);
    
            } elseif (!isset($body['signatureScaling']) || !Validator::oneOf(Validator::falseVal(), Validator::intVal()->between(10, 50))->validate($body['signatureScaling'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body signatureScaling is neither false nor an integer between 10 and 50']);
    
    Damien's avatar
    Damien committed
            $user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
            if (empty($user)) {
                return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
            }
    
    
            $body['notifications']['summaries'] = array_values(array_unique($body['notifications']['summaries']));
    
            $preferences = json_encode([
    
                'lang'             => $body['lang'],
                'writingMode'      => $body['writingMode'],
                'writingSize'      => $body['writingSize'],
                'writingColor'     => $body['writingColor'],
                'signatureScaling' => $body['signatureScaling'],
                'notifications'    => [
                    'instant'      => $body['notifications']['instant'],
                    'summaries'    => $body['notifications']['summaries'],
                ]
    
            ]);
            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([
    
                'code'       => 'OK',
                'objectType' => 'users',
                'objectId'   => $args['id'],
                'type'       => 'MODIFICATION',
                'message'    => "{userUpdated} : {$user['firstname']} {$user['lastname']}"
    
            ]);
    
            return $response->withStatus(204);
        }
    
    
        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([
    
                'code'       => 'OK',
                'objectType' => 'users',
                'objectId'   => $args['id'],
                'type'       => 'MODIFICATION',
                'message'    => "{userUpdated} : {$user['firstname']} {$user['lastname']}"
    
        public function updatePassword(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']);
            }
    
    
            $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']);
            }
    
    
            if ($GLOBALS['id'] != $args['id']) {
    
                if (!UserController::hasRightByUserId(['activeUserId' => $GLOBALS['id'], 'targetUserId' => $args['id']])) {
                    return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
                }
    
            $body = $request->getParsedBody();
            if (!Validator::stringType()->notEmpty()->validate($body['newPassword'])) {
    
    Damien's avatar
    Damien committed
                return $response->withStatus(400)->withJson(['errors' => 'Body newPassword is empty or not a string']);
    
            } elseif ($body['newPassword'] != $body['passwordConfirmation']) {
    
    Damien's avatar
    Damien committed
                return $response->withStatus(400)->withJson(['errors' => 'Body newPassword and passwordConfirmation must be identical']);
    
            if ($user['isRest'] == false) {
    
                if (empty($body['currentPassword']) || !AuthenticationModel::authentication(['login' => $user['login'], 'password' => $body['currentPassword']])) {
    
                    return $response->withStatus(401)->withJson(['errors' => 'Wrong Password', 'lang' => 'wrongCurrentPassword']);
    
            if (!PasswordController::isPasswordValid(['password' => $body['newPassword']])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Password does not match security criteria']);
    
            } elseif (!PasswordModel::isPasswordHistoryValid(['password' => $body['newPassword'], 'userId' => $args['id']])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Password has already been used', 'lang' => 'alreadyUsedPassword']);
    
            UserModel::updatePassword(['id' => $args['id'], 'password' => $body['newPassword']]);
    
            PasswordModel::setHistoryPassword(['userId' => $args['id'], 'password' => $body['newPassword']]);
    
            $refreshToken = [];
            if ($GLOBALS['id'] == $args['id']) {
    
                $refreshJWT     = AuthenticationController::getRefreshJWT();
    
                $refreshToken[] = $refreshJWT;
    
                $response       = $response->withHeader('Token', AuthenticationController::getJWT());
                $response       = $response->withHeader('Refresh-Token', $refreshJWT);
    
            }
    
            UserModel::update([
                'set'   => ['refresh_token' => json_encode($refreshToken)],
                'where' => ['id = ?'],
                'data'  => [$args['id']]
            ]);
    
    
    Damien's avatar
    Damien committed
            HistoryController::add([
    
                'code'       => 'OK',
                'objectType' => 'users',
                'objectId'   => $args['id'],
                'type'       => 'MODIFICATION',
                'message'    => '{userPasswordUpdated}'
    
            return $response->withStatus(204);
    
        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']);
            }
    
    
            $user = UserModel::getByLogin(['select' => ['id', 'email', 'preferences'], 'login' => strtolower($body['login'])]);
    
            if (empty($user)) {
    
                return $response->withStatus(204);
    
            }
    
            $GLOBALS['id'] = $user['id'];
    
    
            $resetToken = AuthenticationController::getResetJWT(['id' => $GLOBALS['id'], 'expirationTime' => 3600]);
    
            UserModel::update(['set' => ['reset_token' => $resetToken], 'where' => ['id = ?'], 'data' => [$user['id']]]);
    
    
            $user['preferences'] = json_decode($user['preferences'], true);
    
    Damien's avatar
    Damien committed
            $lang = LanguageController::get(['lang' => $user['preferences']['lang']]);
    
            $url = ConfigurationModel::getApplicationUrl() . 'dist/#/update-password?token=' . $resetToken;
    
            EmailController::createEmail([
    
                'userId' => $user['id'],
                'data'   => [
                    'sender'     => 'Notification',
                    'recipients' => [$user['email']],
                    'subject'    => $lang['notificationForgotPasswordSubject'],
    
                    'body'       => $lang['notificationForgotPasswordBody'] . '<a href="' . $url . '">'.$url.'</a>' . $lang['notificationForgotPasswordFooter'],
    
                    'isHtml'     => true
    
                ]
            ]);
    
            HistoryController::add([
    
                'code'       => 'OK',
                'objectType' => 'users',
                'objectId'   => $user['id'],
                'type'       => 'MODIFICATION',
                'message'    => '{userPasswordForgotten}'
    
            ]);
    
            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']);
            }
    
    
            try {
    
                $jwt = AuthenticationModel::decodeToken($body['token'], CoreConfigModel::getEncryptKey());
    
                $jwt['user'] = (array)$jwt['user'] ?? [];
    
            } catch (\Exception $e) {
    
                return $response->withStatus(403)->withJson(['errors' => 'Invalid token', 'lang' => 'invalidToken']);
    
            $user = UserModel::getById(['id' => $jwt['user']['id'], 'select' => ['id', 'reset_token']]);
    
            if (empty($user)) {
                return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
            }
    
    
            if ($body['token'] != $user['reset_token']) {
    
                return $response->withStatus(403)->withJson(['errors' => 'Invalid token', 'lang' => 'invalidToken']);
    
            }
    
            if (!PasswordController::isPasswordValid(['password' => $body['password']])) {
                return $response->withStatus(400)->withJson(['errors' => 'Password does not match security criteria']);
            }
    
            UserModel::update([
                'set' => [
    
                    'password'                   => AuthenticationModel::getPasswordHash($body['password']),
                    'password_modification_date' => 'CURRENT_TIMESTAMP',
                    'reset_token'                => null,
                    'refresh_token'              => '[]'
    
                ],
                'where' => ['id = ?'],
    
                'data'  => [$user['id']]
    
            ]);
    
            $GLOBALS['id'] = $user['id'];
            HistoryController::add([
    
                'code'       => 'OK',
                'objectType' => 'users',
                'objectId'   => $user['id'],
                'type'       => 'MODIFICATION',
                'message'    => '{userForgottenPasswordUpdated}'
    
            ]);
    
            return $response->withStatus(204);
        }
    
    
        public function sendAccountActivationNotification(Request $request, Response $response, array $args)
        {
    
            if (empty($args['id']) || !Validator::intVal()->validate($args['id'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
            }
    
    
            if (!UserController::hasRightByUserId(['activeUserId' => $GLOBALS['id'], 'targetUserId' => $args['id']])) {
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
            }
    
    
            $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);
        }
    
    
        public function getManageableGroupsOfCurrentUser(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->withJson(['groups' => $manageableGroups]);
    
    Damien's avatar
    Damien committed
        public static function getUserInformationsById(array $args)
        {
            ValidatorModel::notEmpty($args, ['id']);
            ValidatorModel::intVal($args, ['id']);
    
    
    Damien's avatar
    Damien committed
            $user = UserModel::getById(['select' => ['id', 'login', 'email', 'firstname', 'lastname', 'phone', 'picture', 'preferences', 'substitute', '"isRest"', 'signature_modes', 'x509_fingerprint'], 'id' => $args['id']]);
    
            if (empty($user)) {
                return [];
            }
    
            $user['signatureModes'] = json_decode($user['signature_modes'], true);
            unset($user['signature_modes']);
    
            $validSignatureModes = CoreConfigModel::getSignatureModes();
            $validSignatureModes = array_column($validSignatureModes, 'id');
            $user['signatureModes'] = array_reverse(array_values(array_intersect($validSignatureModes, $user['signatureModes'])));
    
    
            if (empty($user['picture'])) {
    
    Damien's avatar
    Damien committed
                $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
            if ($GLOBALS['id'] == $args['id']) {
    
                $user['preferences']                = json_decode($user['preferences'], true);
                $user['availableLanguages']         = LanguageController::getAvailableLanguages();
    
                $user['administrativePrivileges']   = PrivilegeController::getPrivilegesByUserId(['userId' => $args['id'], 'type' => 'admin']);
    
                $user['appPrivileges']              = PrivilegeController::getPrivilegesByUserId(['userId' => $args['id'], 'type' => 'simple']);
    
                if (!empty($user['substitute'])) {
    
                    $user['substituteUser'] = UserModel::getLabelledUserById(['id' => $user['substitute']]);
    
            $currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);
            if ($currentUser['isRest']) {
                $user['x509Fingerprint'] = $user['x509_fingerprint'];
            }
            unset($user['x509_fingerprint']);
    
    
            $connection = ConfigurationModel::getConnection();
            $user['canSendActivationNotification'] = !$user['isRest'] && $connection == 'default';
    
    
    Damien's avatar
    Damien committed
            return $user;
        }
    
    
        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');
    
                return GroupModel::get();
    
            }
    
            $groups = UserGroupModel::get(['select' => ['group_id'], 'where' => ['user_id = ?'], 'data' => [$args['userId']]]);
    
            $manageableGroups = [];
            foreach ($groups as $group) {
    
                $privilege = GroupPrivilegeModel::getPrivileges([
                    'select' => ['parameters'],
                    'where'  =>  ['group_id = ?', 'privilege = ?'],
                    'data'   => [$group['group_id'], 'manage_users']
                ]);
    
                $parameters = empty($privilege[0]['parameters']) ? [] : json_decode($privilege[0]['parameters'], true);
                $currentGroups = $parameters['authorized'] ?? [];
                $manageableGroups = array_merge($manageableGroups, $currentGroups);
            }
    
            $manageableGroups = array_unique($manageableGroups);
    
            $manageableGroups = GroupModel::get([
                'where' => ['id in (?)'],
                'data'  => [$manageableGroups]
            ]);
    
            return $manageableGroups;
    
        /**
         * hasRightByUserId returns whether activeUser has right on targetUser,
         * with an optional focus targetGroup
         *
         * @return bool
         */
    
        public static function hasRightByUserId(array $args)
        {
            ValidatorModel::notEmpty($args, ['activeUserId', 'targetUserId']);
    
            ValidatorModel::intVal($args, ['activeUserId', 'targetUserId', 'targetGroupId']);
    
            $activeUserManageableGroups = array_column(UserController::getManageableGroups(['userId' => $args['activeUserId']]), 'id');
            if (empty($activeUserManageableGroups)) {
                return false;
            }
    
            $targetUserGroups = array_column(UserGroupModel::get([
    
                'select' => ['group_id'],
                'where'  => ['user_id = ?'],
                'data'   => [$args['targetUserId']]
            ]), 'group_id');
    
            $groupsIntersection = array_intersect($targetUserGroups, $activeUserManageableGroups);
            if (empty($args['targetGroupId'])) {
    
                return $args['activeUserId'] == $args['targetUserId'] || !empty($groupsIntersection);
    
            }
    
            return in_array($args['targetGroupId'], $groupsIntersection);
    
    Florian Azizian's avatar
    Florian Azizian committed
    }