Started work on per-user session recording. #3064

Ylian Saint-Hilaire committed Aug 26, 2021 at 12:42 UTC e6b6bd061e0255d19e8bc02d0287f9387b717e69
7 files changed +92 -25
meshcentral-config-schema.json
+1
@@ -675,6 +675,7 @@
675 "type": "object",
676 "additionalProperties": false,
677 "properties": {
678 + "onlySelectedUsers": { "type": "boolean", "default": false, "description": "When enabled, only device users with the session recording feature turned on will be recorded. When false, all users are recorded." },
679 "onlySelectedDeviceGroups": { "type": "boolean", "default": false, "description": "When enabled, only device groups with the session recording feature turned on will be recorded. When false, all devices are recorded." },
680 "filepath": { "type": "string" },
681 "index": { "type": "boolean", "default": false },
meshdesktopmultiplex.js
+15 -3
@@ -731,9 +731,21 @@ function CreateDesktopMultiplexor(parent, domain, nodeid, func) {
731 if ((domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf(2) >= 0))))) {
732
733 // Check again to make sure we need to start recording
734 - if (domain.sessionrecording.onlyselecteddevicegroups === true) {
735 - var mesh = parent.meshes[obj.meshid];
736 - if ((mesh.flags == null) || ((mesh.flags & 4) == 0)) { func(false); return; } // Do not record the session
734 + if ((domain.sessionrecording.onlyselecteddevicegroups === true) || (domain.sessionrecording.onlyselectedusers === true)) {
735 + var record = false;
736 +
737 + // Check user recording
738 + if (domain.sessionrecording.onlyselectedusers === true) {
739 + // TODO: Check recording ???
740 + }
741 +
742 + // Check device group recording
743 + if (domain.sessionrecording.onlyselecteddevicegroups === true) {
744 + var mesh = parent.meshes[obj.meshid];
745 + if ((mesh.flags != null) && ((mesh.flags & 4) != 0)) { record = true; }
746 + }
747 +
748 + if (record == false) { func(false); return; } // Do not record the session
749 }
750
751 var now = new Date(Date.now());
meshrelay.js
+28 -18
@@ -349,31 +349,41 @@ function CreateMeshRelayEx(parent, ws, req, domain, user, cookie) {
349 xtextSession = 2; // 1 = Raw recording of all strings, 2 = Record chat session messages only.
350 }
351 if ((obj.req.query.p != null) && (obj.req.query.nodeid != null) && (sessionUser != null) && (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf(parseInt(obj.req.query.p)) >= 0))))) { recordSession = true; }
352 -
352 +
353 if (recordSession) {
354 // Get the computer name
355 parent.db.Get(obj.req.query.nodeid, function (err, nodes) {
356 - var xusername = '', xdevicename = '', xdevicename2 = null, node = null;
356 + var xusername = '', xdevicename = '', xdevicename2 = null, node = null, record = true;
357 if ((nodes != null) && (nodes.length == 1)) { node = nodes[0]; xdevicename2 = node.name; xdevicename = '-' + parent.common.makeFilename(node.name); }
358
359 - // Check again if we need to do recording
360 - if ((node == null) || (domain.sessionrecording.onlyselecteddevicegroups === true)) {
361 - var mesh = null;
362 - if (node != null) { mesh = parent.meshes[node.meshid]; }
363 - if ((node == null) || (mesh == null) || (mesh.flags == null) || ((mesh.flags & 4) == 0)) {
364 - // Do not record the session, just send session start
365 - try { ws.send('c'); } catch (ex) { } // Send connect to both peers
366 - try { relayinfo.peer1.ws.send('c'); } catch (ex) { }
367 -
368 - // Send any stored push messages
369 - obj.pushStoredMessages();
370 - relayinfo.peer1.pushStoredMessages();
359 + // Check again if we need to do messenger recording
360 + if ((domain.sessionrecording.onlyselectedusers === true) || (domain.sessionrecording.onlyselecteddevicegroups === true)) {
361 + record = false;
362
372 - // Send other peer's image
373 - obj.sendPeerImage();
374 - relayinfo.peer1.sendPeerImage();
375 - return;
363 + // Check if this device group needs to be recorded
364 + if ((node == null) || (domain.sessionrecording.onlyselecteddevicegroups === true)) {
365 + var mesh = null;
366 + if (node != null) { mesh = parent.meshes[node.meshid]; }
367 + if ((node != null) && (mesh != null) && (mesh.flags != null) && ((mesh.flags & 4) != 0)) { record = true; }
368 }
369 +
370 + // Check if this user needs to be recorded
371 + if ((sessionUser != null) && (sessionUser.flags != null) && ((sessionUser.flags & 2) != 0)) { record = true; }
372 + }
373 +
374 + // Do not record the session, just send session start
375 + if (record == false) {
376 + try { ws.send('c'); } catch (ex) { } // Send connect to both peers
377 + try { relayinfo.peer1.ws.send('c'); } catch (ex) { }
378 +
379 + // Send any stored push messages
380 + obj.pushStoredMessages();
381 + relayinfo.peer1.pushStoredMessages();
382 +
383 + // Send other peer's image
384 + obj.sendPeerImage();
385 + relayinfo.peer1.sendPeerImage();
386 + return;
387 }
388
389 // Get the username and make it acceptable as a filename
meshuser.js
+3 -1
@@ -492,7 +492,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
492 serverinfo.languages = parent.renderLanguages;
493 serverinfo.tlshash = Buffer.from(parent.webCertificateFullHashs[domain.id], 'binary').toString('hex').toUpperCase(); // SHA384 of server HTTPS certificate
494 serverinfo.agentCertHash = parent.agentCertificateHashBase64;
495 - if ((domain.sessionrecording) && (domain.sessionrecording.onlyselecteddevicegroups === true)) { serverinfo.devGroupSessionRecording = 1; } // Allow enabling of session recording
495 + if ((domain.sessionrecording) && (domain.sessionrecording.onlyselectedusers === true)) { serverinfo.usersSessionRecording = 1; } // Allow enabling of session recording for user groups
496 + if ((domain.sessionrecording) && (domain.sessionrecording.onlyselecteddevicegroups === true)) { serverinfo.devGroupSessionRecording = 1; } // Allow enabling of session recording for device groups
497 if ((parent.parent.config.domains[domain.id].amtacmactivation != null) && (parent.parent.config.domains[domain.id].amtacmactivation.acmmatch != null)) {
498 var matchingDomains = [];
499 for (var i in parent.parent.config.domains[domain.id].amtacmactivation.acmmatch) {
@@ -1581,6 +1582,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1582 if (command.resetNextLogin === true) { chguser.passchange = -1; }
1583 if ((command.consent != null) && (typeof command.consent == 'number')) { if (command.consent == 0) { delete chguser.consent; } else { chguser.consent = command.consent; } change = 1; }
1584 if ((command.phone != null) && (typeof command.phone == 'string') && ((command.phone == '') || isPhoneNumber(command.phone))) { if (command.phone == '') { delete chguser.phone; } else { chguser.phone = command.phone; } change = 1; }
1585 + if ((command.flags != null) && (typeof command.flags == 'number')) { if (command.flags == 0) { delete chguser.flags; } else { chguser.flags = command.flags; } change = 1; } // Flags: 1 = Account Image, 2 = Session Recording
1586
1587 // Site admins can change any server rights, user managers can only change AccountLock, NoMeshCmd and NoNewGroups
1588 if (common.validateInt(command.siteadmin) && (chguser._id !== user._id) && (chguser.siteadmin != command.siteadmin)) { // We can't change our own siteadmin permissions.
sample-config-advanced.json
+1
@@ -311,6 +311,7 @@
311 },
312 "_agentConfig": [ "webSocketMaskOverride=1", "coreDumpEnabled=1" ],
313 "_sessionRecording": {
314 + "_onlySelectedUsers": true,
315 "_onlySelectedDeviceGroups": true,
316 "_filepath": "C:\\temp",
317 "_index": true,
views/default.handlebars
+29
@@ -14051,6 +14051,17 @@
14051 x += addDeviceAttribute("Phone Number", (user.phone?user.phone:('<i>' + "None" + '</i>')) + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" onclick=p30editPhone() />');
14052 }
14053
14054 + // Display features
14055 + if (serverinfo.usersSessionRecording == 1) {
14056 + var userFeatures = [];
14057 + if (user.flags) {
14058 + if ((serverinfo.usersSessionRecording == 1) && (user.flags & 2)) { userFeatures.push("Record Sessions"); }
14059 + }
14060 + userFeatures = userFeatures.join(', ');
14061 + if (userFeatures == '') { userFeatures = '<i>' + "None" + '</i>'; }
14062 + x += addDeviceAttribute("Features", addLink(userFeatures, 'p20edituserfeatures()'));
14063 + }
14064 +
14065 x += addDeviceAttribute("Server Rights", premsg + msg.join(', ') + ' <img style=cursor:pointer class=hoverButton onclick=\'return showUserAdminDialog(event,"' + encodeURIComponentEx(user._id) + '")\' src="images/link5.png" />');
14066 if (user.quota) x += addDeviceAttribute("Server Quota", EscapeHtml(parseInt(user.quota) / 1024) + ' k');
14067 x += addDeviceAttribute("Creation", printDateTime(new Date(user.creation * 1000)));
@@ -14165,6 +14176,24 @@
14176 p30editPhoneValidate();
14177 }
14178
14179 + function p20edituserfeatures() {
14180 + if (xxdialogMode) return;
14181 + var flags = (currentUser.flags)?currentUser.flags:0, x = ''; // Flags: 1 = Account Image, 2 = Session Recording
14182 + if (serverinfo.usersSessionRecording == 1) {
14183 + x += '<div><label><input type=checkbox id=d20flag2 onchange=p20edituserfeaturesValidate() ' + ((flags & 2) ? 'checked' : '') + '>' + "Record sessions" + '</label><br></div>';
14184 + }
14185 + setDialogMode(2, "Edit User Features", 3, p20edituserfeaturesEx, x);
14186 + }
14187 +
14188 + function p20edituserfeaturesValidate() { }
14189 +
14190 + // Send to the server the new user's real name
14191 + function p20edituserfeaturesEx() {
14192 + var f = currentUser.flags & 1; // Flags: 1 = Account Image, 2 = Session Recording
14193 + if (Q('d20flag2').checked) { f += 2; }
14194 + meshserver.send({ action: 'edituser', id: currentUser._id, flags: f });
14195 + }
14196 +
14197 function p30editPhoneValidate(x) {
14198 var ok = (Q('d2phoneinput').value == '') || (isPhoneNumber(Q('d2phoneinput').value));
14199 QE('idx_dlgOkButton', ok);
webserver.js
+15 -3
@@ -3943,9 +3943,21 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
3943 if (domain.sessionrecording == true || ((typeof domain.sessionrecording == 'object') && ((domain.sessionrecording.protocols == null) || (domain.sessionrecording.protocols.indexOf((req.query.p == 2) ? 101 : 100) >= 0)))) { // TODO 100
3944 // Check again if we need to do recording
3945 var record = true;
3946 - if (domain.sessionrecording.onlyselecteddevicegroups === true) {
3947 - var mesh = obj.meshes[node.meshid];
3948 - if ((mesh.flags == null) || ((mesh.flags & 4) == 0)) { record = false; } // Do not record the session
3946 +
3947 + // Check user or device group recording
3948 + if ((domain.sessionrecording.onlyselectedusers === true) || (domain.sessionrecording.onlyselecteddevicegroups === true)) {
3949 + record = false;
3950 +
3951 + // Check device group recording
3952 + if (domain.sessionrecording.onlyselecteddevicegroups === true) {
3953 + var mesh = obj.meshes[node.meshid];
3954 + if ((mesh.flags != null) && ((mesh.flags & 4) != 0)) { record = true; } // Record the session
3955 + }
3956 +
3957 + // Check user recording
3958 + if (domain.sessionrecording.onlyselectedusers === true) {
3959 + if ((user.flags != null) && ((user.flags & 2) != 0)) { record = true; } // Record the session
3960 + }
3961 }
3962
3963 if (record == true) {