Newer
Older
* Copyright Maarch since 2008 under license.
* See LICENSE.txt file at the root folder for more details.
* This file is part of Maarch software.
*
*/
/**
* @brief User Controller
* @author dev@maarch.org
*/
namespace User\controllers;
Guillaume Heurtier
committed
use Configuration\models\ConfigurationModel;
use Document\controllers\DigitalSignatureController;
use Document\models\DocumentModel;
use Group\controllers\PrivilegeController;
use Group\models\GroupPrivilegeModel;
use Slim\Psr7\Request;
use SrcCore\http\Response;
use SrcCore\controllers\AuthenticationController;
use SrcCore\controllers\PasswordController;
use SrcCore\models\AuthenticationModel;
use SrcCore\models\PasswordModel;
use User\models\SignatureModel;
use Workflow\models\WorkflowTemplateItemModel;
use Workflow\models\WorkflowTemplateModel;
use Notification\models\NotificationsScheduleModel;
public function get(Request $request, Response $response)
{
$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';
$currentUser = UserModel::getById(['select' => ['"isRest"'], 'id' => $GLOBALS['id']]);

Quentin Ribac
committed
$manageableGroups = UserController::getManageableGroups(['userId' => $GLOBALS['id']]);
$manageableGroups = array_column($manageableGroups, 'id');

Quentin Ribac
committed
$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']);

Quentin Ribac
committed
$users[$key]['groups'] = [];

Quentin Ribac
committed
$groupsIds = array_column(array_filter($allUsersGroups, function ($userGroup) use ($user) {
return $userGroup['user_id'] == $user['id'];
}), 'group_id');

Quentin Ribac
committed
$actuallyAlone = false;
if (empty($groupsIds)) {
$actuallyAlone = true;
} elseif ($GLOBALS['id'] != $user['id']) {

Quentin Ribac
committed
$groupsIds = array_values(array_intersect($groupsIds, $manageableGroups));
}
if (!empty($groupsIds)) {

Quentin Ribac
committed
$users[$key]['groups'] = array_filter($allGroupLabels, function ($group) use ($groupsIds) {
return in_array($group['id'], $groupsIds);
});

Quentin Ribac
committed
} elseif (!$actuallyAlone) {

Quentin Ribac
committed
unset($users[$key]);

Quentin Ribac
committed
}

Quentin Ribac
committed
return $response->withJson(['users' => array_values($users)]);
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']);
}

nicolas lebozec
committed
$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')));
}
Joseph AKEL
committed
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']}"
]);
public function create(Request $request, Response $response)
{
if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
$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'])) {
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'])) {
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') : [];

Quentin Ribac
committed
$manageableGroups = array_column(UserController::getManageableGroups(['userId' => $GLOBALS['id']]), 'id');
$body['groups'] = array_values(array_intersect($body['groups'], $manageableGroups));
$body['login'] = strtolower($body['login']);
$existingUser = UserModel::getByLogin(['login' => $body['login'], 'select' => [1]]);
return $response->withStatus(400)->withJson(['errors' => 'Login already exists', 'lang' => 'userLoginAlreadyExists']);
Jean-Laurent DUZANT
committed
$body['x509_fingerprint'] = !empty($body['x509Fingerprint']) ? $body['x509Fingerprint'] : null;
Jean-Laurent DUZANT
committed
if (empty($body['phone'])) {
Jean-Laurent DUZANT
committed
$body['phone'] = null;
$body['"isRest"'] = true;
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"]);
}
}
}
} else {
$body['signatureModes'] = ['stamp'];
}
$body['signatureModes'] = json_encode($body['signatureModes']);
'code' => 'OK',
'objectType' => 'users',
'objectId' => $id,
'type' => 'CREATION',
'message' => "{userAdded} : {$body['firstname']} {$body['lastname']}"
if (empty($body['isRest'])) {
AuthenticationController::sendAccountActivationNotification(['userId' => $id, 'userEmail' => $body['email']]);
}
public function update(Request $request, Response $response, array $args)
{
Jean-Laurent DUZANT
committed
if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
}
Joseph AKEL
committed
Guillaume Heurtier
committed
$connection = ConfigurationModel::getConnection();
if (($GLOBALS['id'] != $args['id'] || $connection != 'default') && !UserController::hasRightByUserId(['activeUserId' => $GLOBALS['id'], 'targetUserId' => $args['id']])) {

Quentin Ribac
committed
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']);
} 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'])) {
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'])) {

Quentin Ribac
committed
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'],
Joseph AKEL
committed
'phone' => $body['phone'] ?? null,
'signature_modes' => []
$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']);

Florian Azizian
committed
$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']);
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]);
}

Quentin Ribac
committed
'code' => 'OK',
'objectType' => 'users',
'objectId' => $args['id'],
'type' => 'MODIFICATION',
'message' => "{userUpdated} : {$body['firstname']} {$body['lastname']}"
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']);
}
Guillaume Heurtier
committed
$imagick->thumbnailImage(100, 0);
$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']}"
]);
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']);
}
Jean-Laurent DUZANT
committed
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');
'select' => ['main_document_id'],
'where' => ['user_id = ?', "process_date IS NULL", "status IS NULL"],
'data' => [$args['id']]
]);
$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']])) {

Quentin Ribac
committed
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']);
}

Quentin Ribac
committed
$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']);

Quentin Ribac
committed
} 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'])) {

Quentin Ribac
committed
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'])) {

Quentin Ribac
committed
return $response->withStatus(400)->withJson(['errors' => 'Body signatureScaling is neither false nor an integer between 10 and 50']);
$user = UserModel::getById(['id' => $args['id'], 'select' => ['firstname', 'lastname']]);
if (empty($user)) {
return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
}

Quentin Ribac
committed
$body['notifications']['summaries'] = array_values(array_unique($body['notifications']['summaries']));

Quentin Ribac
committed
'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);
}
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
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']}"
]);
return $response->withStatus(204);
}
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']);
}

Florian Azizian
committed
$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']);
}

Quentin Ribac
committed
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'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body newPassword is empty or not a string']);
} elseif ($body['newPassword'] != $body['passwordConfirmation']) {
return $response->withStatus(400)->withJson(['errors' => 'Body newPassword and passwordConfirmation must be identical']);
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();
$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']]
]);
'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'])]);
$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);
$lang = LanguageController::get(['lang' => $user['preferences']['lang']]);
$url = ConfigurationModel::getApplicationUrl() . 'dist/#/update-password?token=' . $resetToken;
'userId' => $user['id'],
'data' => [
'sender' => 'Notification',
'recipients' => [$user['email']],
'subject' => $lang['notificationForgotPasswordSubject'],

Florian Azizian
committed
'body' => $lang['notificationForgotPasswordBody'] . '<a href="' . $url . '">'.$url.'</a>' . $lang['notificationForgotPasswordFooter'],
'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']);
}
Guillaume Heurtier
committed
$jwt = AuthenticationModel::decodeToken($body['token'], CoreConfigModel::getEncryptKey());
$jwt['user'] = (array)$jwt['user'] ?? [];
return $response->withStatus(403)->withJson(['errors' => 'Invalid token', 'lang' => 'invalidToken']);
Guillaume Heurtier
committed
$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' => '[]'
]);
$GLOBALS['id'] = $user['id'];
HistoryController::add([
'code' => 'OK',
'objectType' => 'users',
'objectId' => $user['id'],
'type' => 'MODIFICATION',
'message' => '{userForgottenPasswordUpdated}'
]);
return $response->withStatus(204);
}
Guillaume Heurtier
committed
public function sendAccountActivationNotification(Request $request, Response $response, array $args)
{

Quentin Ribac
committed
if (empty($args['id']) || !Validator::intVal()->validate($args['id'])) {
Guillaume Heurtier
committed
return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
}

Quentin Ribac
committed
if (!UserController::hasRightByUserId(['activeUserId' => $GLOBALS['id'], 'targetUserId' => $args['id']])) {
return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
}
Guillaume Heurtier
committed
$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);
}

Quentin Ribac
committed
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]);
public static function getUserInformationsById(array $args)
{
ValidatorModel::notEmpty($args, ['id']);
ValidatorModel::intVal($args, ['id']);
$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'])));
$user['picture'] = base64_encode(file_get_contents('src/frontend/assets/user_picture.png'));
$user['picture'] = 'data:image/png;base64,' . $user['picture'];
}
$user['preferences'] = json_decode($user['preferences'], true);
$user['availableLanguages'] = LanguageController::getAvailableLanguages();
$user['administrativePrivileges'] = PrivilegeController::getPrivilegesByUserId(['userId' => $args['id'], 'type' => 'admin']);

Florian Azizian
committed
$user['appPrivileges'] = PrivilegeController::getPrivilegesByUserId(['userId' => $args['id'], 'type' => 'simple']);
$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']);
Guillaume Heurtier
committed
$connection = ConfigurationModel::getConnection();
$user['canSendActivationNotification'] = !$user['isRest'] && $connection == 'default';
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) {

Quentin Ribac
committed
$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);

Quentin Ribac
committed
if (empty($manageableGroups)) {
return [];
}
$manageableGroups = GroupModel::get([
'where' => ['id in (?)'],
'data' => [$manageableGroups]
]);
return $manageableGroups;

Quentin Ribac
committed

Quentin Ribac
committed
/**
* hasRightByUserId returns whether activeUser has right on targetUser,
* with an optional focus targetGroup
*
* @return bool
*/

Quentin Ribac
committed
public static function hasRightByUserId(array $args)
{
ValidatorModel::notEmpty($args, ['activeUserId', 'targetUserId']);

Quentin Ribac
committed
ValidatorModel::intVal($args, ['activeUserId', 'targetUserId', 'targetGroupId']);

Quentin Ribac
committed

Quentin Ribac
committed
$activeUserManageableGroups = array_column(UserController::getManageableGroups(['userId' => $args['activeUserId']]), 'id');
if (empty($activeUserManageableGroups)) {
return false;
}
$targetUserGroups = array_column(UserGroupModel::get([

Quentin Ribac
committed
'select' => ['group_id'],
'where' => ['user_id = ?'],
'data' => [$args['targetUserId']]
]), 'group_id');

Quentin Ribac
committed
if (empty($targetUserGroups)) {
return true;
}

Quentin Ribac
committed

Quentin Ribac
committed
$groupsIntersection = array_intersect($targetUserGroups, $activeUserManageableGroups);
if (empty($args['targetGroupId'])) {

Quentin Ribac
committed
return $args['activeUserId'] == $args['targetUserId'] || !empty($groupsIntersection);

Quentin Ribac
committed
}
return in_array($args['targetGroupId'], $groupsIntersection);

Quentin Ribac
committed
}