Skip to content

Commit 4015d9a

Browse files
committed
✨ Wrap setinactive.php in transaction with row locking to prevent race conditions and ensure data integrity
🔒 Add syncAndLock() to setoffline.php to prevent race conditions when updating hide_online status
1 parent 16a3d28 commit 4015d9a

2 files changed

Lines changed: 54 additions & 37 deletions

File tree

lhc_web/modules/lhuser/setinactive.php

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,73 @@
11
<?php
2-
header('Content-type: application/json');
32

4-
$currentUser = erLhcoreClassUser::instance();
5-
$userData = $currentUser->getUserData(true);
3+
header('Content-Type: application/json');
4+
$db = ezcDbInstance::get();
65

7-
if (!isset($_SERVER['HTTP_X_CSRFTOKEN']) || !$currentUser->validateCSFRToken($_SERVER['HTTP_X_CSRFTOKEN'])) {
8-
echo json_encode(array('error' => true, 'active' => true));
9-
exit;
10-
}
6+
try {
7+
$db->beginTransaction();
8+
9+
$currentUser = erLhcoreClassUser::instance();
10+
$userData = $currentUser->getUserData(true);
1111

12-
// We have to check is operator really inactive or it's just a tab trying to set inactive mode
13-
if ($Params['user_parameters']['status'] == 'true') {
14-
$activityTimeout = erLhcoreClassModelUserSetting::getSetting('trackactivitytimeout',-1);
12+
// Lock the user record to prevent race conditions when updating inactive_mode
13+
$userData->syncAndLock();
1514

16-
// If there is no individual setting user global one
17-
if ($activityTimeout == -1) {
18-
$activityTimeout = (int)erLhcoreClassModelChatConfig::fetchCache('activity_timeout')->current_value*60;
15+
if (!isset($_SERVER['HTTP_X_CSRFTOKEN']) || !$currentUser->validateCSFRToken($_SERVER['HTTP_X_CSRFTOKEN'])) {
16+
throw new Exception('Invalid CSFR Token');
1917
}
2018

21-
// Operator was still active in another tab, do nothing
22-
if ($activityTimeout > (time() - $userData->lastd_activity)) {
23-
echo json_encode(array('error' => false, 'active' => true));
24-
exit;
19+
// We have to check is operator really inactive or it's just a tab trying to set inactive mode
20+
if ($Params['user_parameters']['status'] == 'true') {
21+
$activityTimeout = erLhcoreClassModelUserSetting::getSetting('trackactivitytimeout',-1);
22+
23+
// If there is no individual setting user global one
24+
if ($activityTimeout == -1) {
25+
$activityTimeout = (int)erLhcoreClassModelChatConfig::fetchCache('activity_timeout')->current_value*60;
26+
}
27+
28+
// Operator was still active in another tab, do nothing
29+
if ($activityTimeout > (time() - $userData->lastd_activity)) {
30+
$db->commit();
31+
echo json_encode(array('error' => false, 'active' => true));
32+
exit;
33+
}
2534
}
26-
}
2735

28-
$originalInactiveMode = $userData->inactive_mode;
36+
$originalInactiveMode = $userData->inactive_mode;
2937

30-
if ($Params['user_parameters']['status'] == 'true') {
31-
$userData->inactive_mode = 1;
32-
} else {
33-
$userData->inactive_mode = 0;
34-
}
38+
if ($Params['user_parameters']['status'] == 'true') {
39+
$userData->inactive_mode = 1;
40+
} else {
41+
$userData->inactive_mode = 0;
42+
}
3543

36-
erLhcoreClassUser::getSession()->update($userData);
44+
erLhcoreClassUser::getSession()->update($userData);
3745

38-
// Construct temporary object to change inactive modes
39-
$userDataTemp = new stdClass();
40-
$userDataTemp->id = $userData->id;
46+
// Construct temporary object to change inactive modes
47+
$userDataTemp = new stdClass();
48+
$userDataTemp->id = $userData->id;
4149

42-
if ($userData->hide_online == 0) { // change status only if he's not offline manually
43-
$userDataTemp->hide_online = $userData->inactive_mode;
44-
$userDataTemp->always_on = $userData->always_on;
50+
if ($userData->hide_online == 0) { // change status only if he's not offline manually
51+
$userDataTemp->hide_online = $userData->inactive_mode;
52+
$userDataTemp->always_on = $userData->always_on;
4553

46-
erLhcoreClassUserDep::setHideOnlineStatus($userDataTemp);
54+
erLhcoreClassUserDep::setHideOnlineStatus($userDataTemp);
4755

48-
if ($originalInactiveMode != $userData->inactive_mode){
49-
$currentUser->updateLastVisit(time(), $userDataTemp->hide_online == 1 ? 2 : 1); // Went offline OR went online
56+
if ($originalInactiveMode != $userData->inactive_mode){
57+
$currentUser->updateLastVisit(time(), $userDataTemp->hide_online == 1 ? 2 : 1); // Went offline OR went online
58+
}
5059
}
51-
}
5260

53-
erLhcoreClassChatEventDispatcher::getInstance()->dispatch('chat.operator_inactivemode_changed',array('user' => & $userData, 'reason' => 'user_action'));
61+
erLhcoreClassChatEventDispatcher::getInstance()->dispatch('chat.operator_inactivemode_changed',array('user' => & $userData, 'reason' => 'user_action'));
5462

63+
echo json_encode(array('error' => false, 'active' => false));
5564

56-
echo json_encode(array('error' => false, 'active' => false));
65+
$db->commit();
66+
67+
} catch (Exception $e) {
68+
echo json_encode(array('error' => true, 'msg' => $e->getMessage()));
69+
$db->rollback();
70+
}
5771

5872
exit;
5973

lhc_web/modules/lhuser/setoffline.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
}
1414

1515
$userData = $currentUser->getUserData(true);
16+
17+
// Lock the user record to prevent race conditions when updating hide_online
18+
$userData->syncAndLock();
1619

1720
if ($Params['user_parameters']['status'] == 'false') {
1821
$userData->hide_online = 0;

0 commit comments

Comments
 (0)