Added per-domain configuration of the MyServer action tab.

Ylian Saint-Hilaire committed Sep 28, 2020 at 13:28 UTC 00ea208f0eeb3d0ee617380c806afbbb2c9ecd3b
6 files changed +91 -18
meshcentral-config-schema.json
+17 -1
@@ -170,7 +170,23 @@
170 "welcomePicture": { "type": "string", "description": "Name of the PNG or JPEG file that will be shown on the login screen. Put this file in the meshcentral-data folder and place the file name here." },
171 "hide": { "type": "integer" },
172 "footer": { "type": "string" },
173 - "certUrl": { "type": "string", "format": "uri", "description": "https url when to get the TLS certificate that MeshAgent's will see when connecting to this server. This setting is used when a reverse proxy like NGINX is used in front of MeshCentral." },
173 + "certUrl": {
174 + "type": "string",
175 + "format": "uri",
176 + "description": "https url when to get the TLS certificate that MeshAgent's will see when connecting to this server. This setting is used when a reverse proxy like NGINX is used in front of MeshCentral."
177 + },
178 + "myServer": {
179 + "type": "object",
180 + "additionalProperties": false,
181 + "properties": {
182 + "Backup": { "type": "boolean", "default": true, "description": "Allows administrators to backup the server from the My Server tab." },
183 + "Restore": { "type": "boolean", "default": true, "description": "Allows administrators to restore the server from the My Server tab." },
184 + "Upgrade": { "type": "boolean", "default": true, "description": "Allows administrators to update the server from the My Server tab." },
185 + "ShowLog": { "type": "boolean", "default": true, "description": "Allows administrators to see the server crash log the server from the My Server tab." },
186 + "Console": { "type": "boolean", "default": true, "description": "Allows administrators to access the server console from the My Server tab." },
187 + "Trace": { "type": "boolean", "default": true, "description": "Allows administrators to access the server trace tab from from the My Server tab." }
188 + }
189 + },
190 "passwordRequirements": {
191 "type": "object",
192 "properties": {
meshuser.js
+14 -2
@@ -444,8 +444,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
444 try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: parent.CloneSafeUser(parent.users[user._id]) })); } catch (ex) { }
445
446 if (user.siteadmin === SITERIGHT_ADMIN) {
447 - // Send server tracing information
448 - try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { }
447 + // Check if tracing is allowed for this domain
448 + if ((domain.myserver == null) || (domain.myserver.trace === true)) {
449 + // Send server tracing information
450 + try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { }
451 + }
452
453 // Send any server warnings if any
454 var serverWarnings = parent.parent.getServerWarnings();
@@ -807,6 +810,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
810 // This is a server console message, only process this if full administrator
811 if (user.siteadmin != SITERIGHT_ADMIN) break;
812
813 + // Only accept is the console is allowed for this domain
814 + if ((domain.myserver != null) && (domain.myserver.console !== true)) break;
815 +
816 var r = '';
817 var cmdargs = splitArgs(command.value);
818 if (cmdargs.length == 0) break;
@@ -2600,6 +2606,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2606 {
2607 // Check the server version
2608 if ((user.siteadmin & 16) == 0) break;
2609 + if ((domain.myserver != null) && (domain.myserver.upgrade !== true)) break;
2610 //parent.parent.getLatestServerVersion(function (currentVersion, latestVersion) { try { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); } catch (ex) { } });
2611 parent.parent.getServerTags(function (tags, err) { try { ws.send(JSON.stringify({ action: 'serverversion', tags: tags })); } catch (ex) { } });
2612 break;
@@ -2608,6 +2615,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2615 {
2616 // Perform server update
2617 if ((user.siteadmin & 16) == 0) break;
2618 + if ((domain.myserver != null) && (domain.myserver.upgrade !== true)) break;
2619 if ((command.version != null) && (typeof command.version != 'string')) break;
2620 parent.parent.performServerUpdate(command.version);
2621 break;
@@ -2616,6 +2624,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
2624 {
2625 // Load the server error log
2626 if ((user.siteadmin & 16) == 0) break;
2627 + if ((domain.myserver != null) && (domain.myserver.errorlog !== true)) break;
2628 fs.readFile(parent.parent.getConfigFilePath('mesherrors.txt'), 'utf8', function (err, data) { try { ws.send(JSON.stringify({ action: 'servererrors', data: data })); } catch (ex) { } });
2629 break;
2630 }
@@ -4512,6 +4521,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
4521 break;
4522 }
4523 case 'traceinfo': {
4524 + // Only accept is the tracing is allowed for this domain
4525 + if ((domain.myserver != null) && (domain.myserver.trace !== true)) break;
4526 +
4527 if ((user.siteadmin === SITERIGHT_ADMIN) && (typeof command.traceSources == 'object')) {
4528 parent.parent.debugRemoteSources = command.traceSources;
4529 parent.parent.DispatchEvent(['*'], obj, { action: 'traceinfo', userid: user._id, username: user.name, traceSources: command.traceSources, nolog: 1, domain: domain.id });
sample-config-advanced.json
+8
@@ -140,6 +140,14 @@
140 "_hide": 4,
141 "_footer": "<a href='https://twitter.com/mytwitter'>Twitter</a>",
142 "_certUrl": "https://192.168.2.106:443/",
143 + "myServer": {
144 + "Backup": false,
145 + "Restore": false,
146 + "Upgrade": false,
147 + "ErrorLog": false,
148 + "Console": false,
149 + "Trace": false
150 + },
151 "_passwordRequirements": {
152 "min": 8,
153 "max": 128,
translate/translate.json
+1 -1
@@ -31786,7 +31786,7 @@
31786 "zh-chs": "服务器统计",
31787 "zh-cht": "伺服器統計",
31788 "xloc": [
31789 - "default.handlebars->container->column_l->p6->p6info->6"
31789 + "default.handlebars->container->column_l->p6->p6info->5"
31790 ]
31791 },
31792 {
views/default.handlebars
+10 -9
@@ -474,8 +474,9 @@
474 <div id="p2ServerActionsVersion"><div class="p2AccountActions"><span style="display:none"><strong>&#x2713;</strong></span></div><a href=# onclick="return server_showVersionDlg()">Check server version</a></div>
475 <div id="p2ServerActionsErrors"><div class="p2AccountActions"><span style="display:none"><strong>&#x2713;</strong></span></div><a href=# onclick="return server_showErrorsDlg()">Show server error log</a></div>
476 </div>
477 + <br />
478 </div>
478 - <br /><strong>Server Statistics</strong><br /><br />
479 + <strong>Server Statistics</strong><br /><br />
480 <div id="serverStats">
481 <div id="serverCpuChartView" style="display:none">
482 <div class="chartViewCanvas"><canvas id="serverCpuChart"></canvas></div>
@@ -1809,10 +1810,9 @@
1810 }
1811
1812 function updateSiteAdmin() {
1812 - var noServerBackup = '{{{noServerBackup}}}';
1813 + var serverFeatures = parseInt('{{{serverfeatures}}}');
1814 var siteRights = userinfo.siteadmin;
1815 var accountSettingsLocked = ((siteRights != 0xFFFFFFFF) && ((siteRights & 1024) != 0));
1815 - if (noServerBackup == 1) { siteRights &= 0xFFFFFFFA; } // If not server backups allowed, remove server backup and restore permissions
1816
1817 // Update account actions
1818 QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0) && (accountSettingsLocked == false)); // Hide Account Security if in single user mode or domain authentication, 2 factor auth not supported.
@@ -1823,12 +1823,13 @@
1823 QV('p2AccountPassActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (userinfo != null) && (userinfo._id.split('/')[2].startsWith('~') == false)); // Hide Account Actions if in single user mode or domain authentication
1824 //QV('p2AccountImage', ((features & 4) == 0) && (serverinfo.domainauth == false)); // If account actions are not visible, also remove the image on that panel
1825 QV('p2AccountImage', !accountSettingsLocked)
1826 - QV('p2ServerActions', siteRights & 21);
1826 + QV('p2ServerActions', (siteRights & 21) && ((serverFeatures & 15) != 0));
1827 QV('LeftMenuMyServer', siteRights & 21); // 16 + 4 + 1
1828 QV('MainMenuMyServer', siteRights & 21);
1829 - QV('p2ServerActionsBackup', siteRights & 1);
1830 - QV('p2ServerActionsRestore', siteRights & 4);
1831 - QV('p2ServerActionsVersion', siteRights & 16);
1829 + QV('p2ServerActionsBackup', (siteRights & 1) && ((serverFeatures & 1) != 0));
1830 + QV('p2ServerActionsRestore', (siteRights & 4) && ((serverFeatures & 2) != 0));
1831 + QV('p2ServerActionsVersion', (siteRights & 16) && ((serverFeatures & 4) != 0));
1832 + QV('p2ServerActionsErrors', (siteRights & 16) && ((serverFeatures & 8) != 0));
1833 QV('MainMenuMyFiles', siteRights & 8);
1834 QV('LeftMenuMyFiles', siteRights & 8);
1835 if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { setDialogMode(0); go(1); }
@@ -1847,8 +1848,8 @@
1848 if (xxcurrentView == 4 || ((xxcurrentView >= 30) && (xxcurrentView < 40))) { setDialogMode(0); go(1); currentUser = null; }
1849 }
1850 meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
1850 - QV('ServerConsole', userinfo.siteadmin === 0xFFFFFFFF);
1851 - QV('ServerTrace', userinfo.siteadmin === 0xFFFFFFFF);
1851 + QV('ServerConsole', (userinfo.siteadmin === 0xFFFFFFFF) && ((serverFeatures & 16) != 0));
1852 + QV('ServerTrace', (userinfo.siteadmin === 0xFFFFFFFF) && ((serverFeatures & 32) != 0));
1853 if ((xxcurrentView == 115) && (userinfo.siteadmin != 0xFFFFFFFF)) { go(6); }
1854 if ((xxcurrentView == 6) && ((userinfo.siteadmin & 21) == 0)) { go(1); }
1855
webserver.js
+41 -5
@@ -2370,8 +2370,41 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
2370 var customui = '';
2371 if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
2372
2373 + // Server features
2374 + var serverFeatures = 63;
2375 + if (domain.myserver) {
2376 + if (domain.myserver.backup !== true) { serverFeatures -= 1; } // Allow server backups
2377 + if (domain.myserver.restore !== true) { serverFeatures -= 2; } // Allow server restore
2378 + if (domain.myserver.upgrade !== true) { serverFeatures -= 4; } // Allow server upgrade
2379 + if (domain.myserver.errorlog !== true) { serverFeatures -= 8; } // Allow show server crash log
2380 + if (domain.myserver.console !== true) { serverFeatures -= 16; } // Allow server console
2381 + if (domain.myserver.trace !== true) { serverFeatures -= 32; } // Allow server tracing
2382 + }
2383 +
2384 // Refresh the session
2374 - render(req, res, getRenderPage('default', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: authRelayCookie, viewmode: viewmode, currentNode: currentNode, logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27'), domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, customui: customui, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate).replace(/'/g, '%27'), amtscanoptions: amtscanoptions, pluginHandler: (parent.pluginHandler == null) ? 'null' : parent.pluginHandler.prepExports() }, req, domain));
2385 + render(req, res, getRenderPage('default', req, domain), getRenderArgs({
2386 + authCookie: authCookie,
2387 + authRelayCookie: authRelayCookie,
2388 + viewmode: viewmode,
2389 + currentNode: currentNode,
2390 + logoutControls: encodeURIComponent(JSON.stringify(logoutcontrols)).replace(/'/g, '%27'),
2391 + domain: domain.id,
2392 + debuglevel: parent.debugLevel,
2393 + serverDnsName: obj.getWebServerName(domain),
2394 + serverRedirPort: args.redirport,
2395 + serverPublicPort: httpsPort,
2396 + serverfeatures: serverFeatures,
2397 + features: features,
2398 + sessiontime: args.sessiontime,
2399 + mpspass: args.mpspass,
2400 + passRequirements: passRequirements,
2401 + customui: customui,
2402 + webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'),
2403 + footer: (domain.footer == null) ? '' : domain.footer,
2404 + webstate: encodeURIComponent(webstate).replace(/'/g, '%27'),
2405 + amtscanoptions: amtscanoptions,
2406 + pluginHandler: (parent.pluginHandler == null) ? 'null' : parent.pluginHandler.prepExports()
2407 + }, req, domain));
2408 });
2409 } else {
2410 // Send back the login application
@@ -4016,7 +4049,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4049 const domain = checkUserIpAddress(req, res);
4050 if (domain == null) { return; }
4051 if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4019 - if ((!req.session) || (req.session == null) || (!req.session.userid) || (obj.parent.args.noserverbackup == 1)) { res.sendStatus(401); return; }
4052 + if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
4053 + if ((domain.myserver != null) && (domain.myserver.backup !== true)) { res.sendStatus(401); return; }
4054 +
4055 var user = obj.users[req.session.userid];
4056 if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights
4057
@@ -4044,7 +4079,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4079 const domain = checkUserIpAddress(req, res);
4080 if (domain == null) { return; }
4081 if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
4047 - if (obj.parent.args.noserverbackup == 1) { res.sendStatus(401); return; }
4082 + if ((domain.myserver != null) && (domain.myserver.restore !== true)) { res.sendStatus(401); return; }
4083 +
4084 var authUserid = null;
4085 if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
4086 const multiparty = require('multiparty');
@@ -4769,8 +4805,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4805 obj.app.get(url, handleRootRequest);
4806 obj.app.post(url, handleRootPostRequest);
4807 obj.app.get(url + 'refresh.ashx', function (req, res) { res.sendStatus(200); });
4772 - obj.app.get(url + 'backup.zip', handleBackupRequest);
4773 - obj.app.post(url + 'restoreserver.ashx', handleRestoreRequest);
4808 + if ((domain.myserver == null) || (domain.myserver.backup === true)) { obj.app.get(url + 'backup.zip', handleBackupRequest); }
4809 + if ((domain.myserver == null) || (domain.myserver.restore === true)) { obj.app.post(url + 'restoreserver.ashx', handleRestoreRequest); }
4810 obj.app.get(url + 'terms', handleTermsRequest);
4811 obj.app.get(url + 'xterm', handleXTermRequest);
4812 obj.app.post(url + 'login', handleLoginRequest);