Guest desktop sharing with time range.

This commit is contained in:
Ylian Saint-Hilaire 2020-11-02 12:11:45 -08:00
parent 1295892e29
commit aceeb285a6
11 changed files with 2126 additions and 1989 deletions

View File

@ -226,7 +226,6 @@
"geoLocation": { "type": "boolean", "default": false, "description": "Enables the geo-location feature and device location map in the user interface, this feature is not being worked on." },
"novnc": { "type": "boolean", "default": true, "description": "When enabled, activates the built-in web-based noVNC client." },
"mstsc": { "type": "boolean", "default": false, "description": "When enabled, activates the built-in web-based RDP client." },
"maxGuestSessionSharingTime": { "type": "integer", "default": 60, "minimum": 1, "maximum": 5760, "description": "Maximum amount of time in minutes that a remote desktop session can be shared with a guest." },
"webEmailsPath": { "type": "string", "description": "Path where to find custom email templates for this domain." },
"customUI": { "type": "object" },
"consentMessages": {

View File

@ -446,7 +446,6 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// Build server information object
var serverinfo = { domain: domain.id, name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (args.lanonly != true) && (parent.certificates.CommonName != null) && (parent.certificates.CommonName.indexOf('.') != -1) && (user._id.split('/')[2].startsWith('~') == false)), domainauth: (domain.auth == 'sspi'), serverTime: Date.now() };
serverinfo.maxGuestSessionSharingTime = (typeof domain.maxguestsessionsharingtime == 'number') ? domain.maxguestsessionsharingtime : 60;
serverinfo.languages = parent.renderLanguages;
serverinfo.tlshash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex').toUpperCase(); // SHA384 of server HTTPS certificate
if ((domain.sessionrecording) && (domain.sessionrecording.onlyselecteddevicegroups === true)) { serverinfo.devGroupSessionRecording = 1; } // Allow enabling of session recording
@ -4676,11 +4675,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
break;
}
case 'createDeviceShareLink': {
var err = null, maxExpireMinutes = (typeof domain.maxguestsessionsharingtime == 'number') ? domain.maxguestsessionsharingtime : 60;
var err = null;
if (common.validateString(command.nodeid, 8, 128) == false) { err = 'Invalid node id'; } // Check the nodeid
else if (common.validateString(command.guestname, 1, 128) == false) { err = 'Invalid guest name'; } // Check the guest name
else if (common.validateInt(command.expire, 1, maxExpireMinutes) == false) { err = 'Invalid expire time'; } // Check the expire time in hours
else if ((command.expire != null) && (typeof command.expire != 'number')) { err = 'Invalid expire time'; } // Check the expire time in hours
else if ((command.start != null) && (typeof command.start != 'number')) { err = 'Invalid start time'; } // Check the start time in seconds
else if ((command.end != null) && (typeof command.end != 'number')) { err = 'Invalid end time'; } // Check the end time in seconds
else if (common.validateInt(command.consent, 0, 256) == false) { err = 'Invalid flags'; } // Check the flags
else if ((command.expire == null) && ((command.start == null) || (command.end == null) || (command.start > command.end))) { err = 'No time specified'; } // Check that a time range is present
else {
if (command.nodeid.split('/').length == 1) { command.nodeid = 'node/' + domain.id + '/' + command.nodeid; }
var snode = command.nodeid.split('/');
@ -4702,10 +4704,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if ((rights != 0xFFFFFFFF) && ((rights & 4352) != 0)) return;
// Create cookie
var publicid = getRandomPassword();
var startTime = Date.now(), expireTime = Date.now() + (60000 * command.expire);
var publicid = getRandomPassword(), startTime, expireTime;
if (command.expire != null) {
// Now until expire in hours
startTime = Date.now();
expireTime = Date.now() + (60000 * command.expire);
} else {
// Time range in seconds
startTime = command.start * 1000;
expireTime = command.end * 1000;
}
const inviteCookie = parent.parent.encodeCookie({ a: 5, uid: user._id, gn: command.guestname, nid: node._id, cf: command.consent, start: startTime, expire: expireTime, pid: publicid }, parent.parent.invitationLinkEncryptionKey);
if (inviteCookie == null) { if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'createDeviceShareLink', responseid: command.responseid, result: 'Unable to generate shareing cookie' })); } catch (ex) { } } return; }
command.start = startTime;
command.expire = expireTime;
// Create the server url

File diff suppressed because one or more lines are too long

13
public/styles/flatpickr.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -166,7 +166,6 @@
"_geoLocation": true,
"_novnc": false,
"_mstsc": true,
"_maxGuestSessionSharingTime": 5760,
"_WebEmailsPath": "/myserver/email-templates",
"_consentMessages": {
"title": "MeshCentral",

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
<link type="text/css" href="styles/ol.css" media="screen" rel="stylesheet" title="CSS" />
<link type="text/css" href="styles/ol3-contextmenu.min.css" media="screen" rel="stylesheet" title="CSS" />
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
<link type="text/css" href="styles/flatpickr.min.css" media="screen" rel="stylesheet" title="CSS" >
<link rel="apple-touch-icon" href="/favicon-303x303.png" />
<script type="text/javascript" src="scripts/common-0.0.1{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/meshcentral{{{min}}}.js"></script>
@ -31,6 +32,7 @@
<script type="text/javascript" src="scripts/qrcode.min.js"></script>
<script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
<script type="text/javascript" src="scripts/flatpickr.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/u2f-api{{{min}}}.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/charts{{{min}}}.js"></script>
<script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
@ -3071,6 +3073,7 @@
if (node == null) break;
x += addHtmlValue("Device", node.name);
x += addHtmlValue("Guest Name", message.guestname);
x += addHtmlValue("Start Time", printDateTime(new Date(message.start)));
x += addHtmlValue("Expire Time", printDateTime(new Date(message.expire)));
var y = [];
if (message.consent & 1) { y.push("Notify"); }
@ -6157,6 +6160,7 @@
meshserver.send({ action: 'toast', nodeids: [ currentNode._id ], title: decodeURIComponent('{{{extitle}}}'), msg: Q('d2devToast').value });
}
/*
function showShareDevice() {
if (xxdialogMode) return;
var y = '', x = "Creates a link that allows a guest without an account to remote desktop into this device for up to 1 hour." + '<br /><br />';
@ -6168,9 +6172,45 @@
setDialogMode(2, "Share Device", 3, showShareDeviceEx, x);
showShareDeviceValidate();
}
*/
function showShareDeviceValidate() { QE('idx_dlgOkButton', Q('d2inviteName').value.trim().length > 0); }
function showShareDeviceEx() { meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), expire: parseInt(Q('d2inviteExpire').value), consent: parseInt(Q('d2userConsent').value) }); }
function showShareDevice() {
if (xxdialogMode) return;
var y = '', x = "Creates a link that allows a guest without an account to remote desktop into this device for a limited time." + '<br /><br />';
x += addHtmlValue("Guest Name", '<input id=d2inviteName style=width:250px maxlength=128 type=text onkeyup=showShareDeviceValidate() />');
var options = { 1 : "1 minute", 5 : "5 minutes", 10 : "10 minutes", 15 : "15 minutes", 30 : "30 minutes", 45 : "45 minutes", 60 : "60 minutes", 120 : "2 hours", 240 : "4 hours", 480 : "8 hours", 720 : "12 hours", 960 : "16 hours", 1440 : "24 hours", 2880 : "2 days", 5760 : "4 days" }
for (var i in options) { y += '<option value=' + i + '>' + options[i] + '</option>'; }
x += addHtmlValue("Validity", '<select id=d2timeRange style=float:right;width:250px onchange=showShareDeviceValidate()><option value=0>' + "Starting now" + '</option><option value=1>' + "Time range" + '</option></select>');
x += '<div id=d2modenow>';
x += addHtmlValue("Expire Time", '<select id=d2inviteExpire style=float:right;width:250px>' + y + '</select>');
x += '</div><div id=d2moderange style=display:none>';
x += addHtmlValue("Time Range", '<input id=d2timeRangeSelector style=float:right;width:250px class=flatpickr type="text" placeholder="Select Date & Time.." data-id="altinput">');
x += '</div>';
x += addHtmlValue("User Consent", '<select id=d2userConsent style=float:right;width:250px><option value=73>' + "Prompt for consent" + '</option><option value=65>' + "Notify Only" + '</option></select>');
setDialogMode(2, "Share Device", 3, showShareDeviceEx, x);
showShareDeviceValidate();
var tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
var rangeTime = flatpickr('#d2timeRangeSelector', { mode: 'range', enableTime: true, minDate: new Date(), defaultDate: [ new Date(), tomorrow ] });
xxdialogTag = rangeTime;
}
function showShareDeviceValidate() {
QV('d2modenow', Q('d2timeRange').value == 0);
QV('d2moderange', Q('d2timeRange').value == 1);
var ok = true;
if (Q('d2inviteName').value.trim().length == 0) { ok = false; }
QE('idx_dlgOkButton', ok);
}
function showShareDeviceEx(b, tag) {
if (Q('d2timeRange').value == 0) {
meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), expire: parseInt(Q('d2inviteExpire').value), consent: parseInt(Q('d2userConsent').value) });
} else {
meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), start: Math.floor(tag.selectedDates[0].getTime() / 1000), end: Math.floor(tag.selectedDates[1].getTime() / 1000), consent: parseInt(Q('d2userConsent').value) });
}
}
function deviceActionFunction() {
if (xxdialogMode) return;

View File

@ -172,7 +172,7 @@
var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel&reg; AMT Connected"];
var webPageFullScreen = false;
var expire = '{{{expire}}}';
if (expire != '') { QH('p11power', format("Expires at {0}", printTime(new Date(parseInt(expire))))) }
if (expire != '') { QH('p11power', printFlexDateTime(new Date(parseInt(expire)))); }
function start() {
window.onresize = deskAdjust;
@ -1161,6 +1161,7 @@
function printDate(d) { return d.toLocaleDateString(urlargs.locale); }
function printTime(d) { return d.toLocaleTimeString(urlargs.locale); }
function printDateTime(d) { return d.toLocaleString(urlargs.locale); }
function printFlexDateTime(d) { if (printDate(new Date()) == printDate(d)) { return format("Expires at {0}", printTime(d)); } else { return format("Expires {0}", printDateTime(d)); } }
start();
</script>

View File

@ -27,7 +27,7 @@
<div id=page_content style=max-height:calc(100vh-138px)>
<div id=column_l>
<h1 id="mainTitle"></h1>
<p style=margin-left:20px id="mainMessage"></p>
<p id="mainMessage"></p>
<br />
</div>
<div id=footer>
@ -52,6 +52,7 @@
var title = '';
if (titleid == 1) { title = "Account Verification"; }
if (titleid == 2) { title = "Desktop Sharing"; }
QH('topTitle', Q('topTitle').innerText + ' - ' + title);
QH('mainTitle', title);
@ -71,8 +72,13 @@
}
case 9: { msg = "ERROR: Invalid account check."; break; }
case 10: { msg = "ERROR: Invalid account check, verification url is only valid for 30 minutes."; break; }
case 11: { msg = "Sharing link not valid yet."; break; }
case 12: { msg = "Sharing link is expired."; break; }
}
QH('mainMessage', msg + ' <a href="' + domainurl + (urlargs.key ? ('?key=' + urlargs.key) : '') + '">' + "Go to login page" + '</a>.');
// Add login page link
if ((msgid != 11) && (msgid != 12)) { msg += ' <a href="' + domainurl + (urlargs.key ? ('?key=' + urlargs.key) : '') + '">' + "Go to login page" + '</a>.' }
QH('mainMessage', msg);
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };

View File

@ -31,7 +31,7 @@
<tr>
<td style="width:100%;text-align:center;color:#c8c8c8;font-size:larger">
<h1 id="mainTitle"></h1>
<p style=margin-left:20px id="mainMessage"></p>
<p id="mainMessage"></p>
</td>
</tr>
<tr style="height:20px">
@ -53,6 +53,7 @@
var title = '';
if (titleid == 1) { title = "Account Verification"; }
if (titleid == 2) { title = "Desktop Sharing"; }
QH('topTitle', Q('topTitle').innerText + ' - ' + title);
QH('mainTitle', title);
@ -72,8 +73,13 @@
}
case 9: { msg = "ERROR: Invalid account check."; break; }
case 10: { msg = "ERROR: Invalid account check, verification url is only valid for 30 minutes."; break; }
case 11: { msg = "Sharing link not valid yet."; break; }
case 12: { msg = "Sharing link is expired."; break; }
}
QH('mainMessage', msg + '<br /><br /><a href="' + domainurl + (urlargs.key ? ('?key=' + urlargs.key) : '') + '">' + "Go to login page" + '</a>.');
// Add login page link
if ((msgid != 11) && (msgid != 12)) { msg += ' <a href="' + domainurl + (urlargs.key ? ('?key=' + urlargs.key) : '') + '">' + "Go to login page" + '</a>.' }
QH('mainMessage', msg);
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };

View File

@ -2922,19 +2922,23 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Check the inbound desktop sharing cookie
var c = obj.parent.decodeCookie(req.query.c, obj.parent.invitationLinkEncryptionKey, 60); // 60 minute timeout
if ((c == null) || (c.a !== 5) || (typeof c.uid != 'string') || (typeof c.nid != 'string') || (typeof c.gn != 'string') || (typeof c.cf != 'number') || (typeof c.start != 'number') || (typeof c.expire != 'number') || (typeof c.pid != 'string') || (c.expire <= Date.now())) { res.sendStatus(404); return; }
if ((c == null) || (c.a !== 5) || (typeof c.uid != 'string') || (typeof c.nid != 'string') || (typeof c.gn != 'string') || (typeof c.cf != 'number') || (typeof c.start != 'number') || (typeof c.expire != 'number') || (typeof c.pid != 'string')) { res.sendStatus(404); return; }
// Check the start time
if ((c.start > Date.now()) || (c.start > c.expire)) { res.sendStatus(404); return; }
// Check the expired time, expire message.
if (c.expire <= Date.now()) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
// Check the public id
obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) {
if ((err != null) || (docs.length == 0)) { res.sendStatus(404); return; }
// Check if any desktop sharing links are present, expire message.
if ((err != null) || (docs.length == 0)) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
// Search for the device share public identifier
// Search for the device share public identifier, expire message.
var found = false;
for (var i = 0; i < docs.length; i++) { if (docs[i].publicid == c.pid) { found = true; } }
if (found == false) { res.sendStatus(404); return; }
if (found == false) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
// Check the start time, not yet valid message.
if ((c.start > Date.now()) || (c.start > c.expire)) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 11, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
// Looks good, let's create the outbound session cookies.
// Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.