Added per-domain configuration of the MyServer action tab.

This commit is contained in:
Ylian Saint-Hilaire 2020-09-28 13:28:08 -07:00
parent 4588f5df66
commit 2a21bbe914
6 changed files with 91 additions and 18 deletions

View File

@ -170,7 +170,23 @@
"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." }, "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." },
"hide": { "type": "integer" }, "hide": { "type": "integer" },
"footer": { "type": "string" }, "footer": { "type": "string" },
"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." }, "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."
},
"myServer": {
"type": "object",
"additionalProperties": false,
"properties": {
"Backup": { "type": "boolean", "default": true, "description": "Allows administrators to backup the server from the My Server tab." },
"Restore": { "type": "boolean", "default": true, "description": "Allows administrators to restore the server from the My Server tab." },
"Upgrade": { "type": "boolean", "default": true, "description": "Allows administrators to update the server from the My Server tab." },
"ShowLog": { "type": "boolean", "default": true, "description": "Allows administrators to see the server crash log the server from the My Server tab." },
"Console": { "type": "boolean", "default": true, "description": "Allows administrators to access the server console from the My Server tab." },
"Trace": { "type": "boolean", "default": true, "description": "Allows administrators to access the server trace tab from from the My Server tab." }
}
},
"passwordRequirements": { "passwordRequirements": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -444,8 +444,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: parent.CloneSafeUser(parent.users[user._id]) })); } catch (ex) { } try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: parent.CloneSafeUser(parent.users[user._id]) })); } catch (ex) { }
if (user.siteadmin === SITERIGHT_ADMIN) { if (user.siteadmin === SITERIGHT_ADMIN) {
// Send server tracing information // Check if tracing is allowed for this domain
try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { } if ((domain.myserver == null) || (domain.myserver.trace === true)) {
// Send server tracing information
try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { }
}
// Send any server warnings if any // Send any server warnings if any
var serverWarnings = parent.parent.getServerWarnings(); var serverWarnings = parent.parent.getServerWarnings();
@ -807,6 +810,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
// This is a server console message, only process this if full administrator // This is a server console message, only process this if full administrator
if (user.siteadmin != SITERIGHT_ADMIN) break; if (user.siteadmin != SITERIGHT_ADMIN) break;
// Only accept is the console is allowed for this domain
if ((domain.myserver != null) && (domain.myserver.console !== true)) break;
var r = ''; var r = '';
var cmdargs = splitArgs(command.value); var cmdargs = splitArgs(command.value);
if (cmdargs.length == 0) break; if (cmdargs.length == 0) break;
@ -2600,6 +2606,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
{ {
// Check the server version // Check the server version
if ((user.siteadmin & 16) == 0) break; if ((user.siteadmin & 16) == 0) break;
if ((domain.myserver != null) && (domain.myserver.upgrade !== true)) break;
//parent.parent.getLatestServerVersion(function (currentVersion, latestVersion) { try { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); } catch (ex) { } }); //parent.parent.getLatestServerVersion(function (currentVersion, latestVersion) { try { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); } catch (ex) { } });
parent.parent.getServerTags(function (tags, err) { try { ws.send(JSON.stringify({ action: 'serverversion', tags: tags })); } catch (ex) { } }); parent.parent.getServerTags(function (tags, err) { try { ws.send(JSON.stringify({ action: 'serverversion', tags: tags })); } catch (ex) { } });
break; break;
@ -2608,6 +2615,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
{ {
// Perform server update // Perform server update
if ((user.siteadmin & 16) == 0) break; if ((user.siteadmin & 16) == 0) break;
if ((domain.myserver != null) && (domain.myserver.upgrade !== true)) break;
if ((command.version != null) && (typeof command.version != 'string')) break; if ((command.version != null) && (typeof command.version != 'string')) break;
parent.parent.performServerUpdate(command.version); parent.parent.performServerUpdate(command.version);
break; break;
@ -2616,6 +2624,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
{ {
// Load the server error log // Load the server error log
if ((user.siteadmin & 16) == 0) break; if ((user.siteadmin & 16) == 0) break;
if ((domain.myserver != null) && (domain.myserver.errorlog !== true)) break;
fs.readFile(parent.parent.getConfigFilePath('mesherrors.txt'), 'utf8', function (err, data) { try { ws.send(JSON.stringify({ action: 'servererrors', data: data })); } catch (ex) { } }); fs.readFile(parent.parent.getConfigFilePath('mesherrors.txt'), 'utf8', function (err, data) { try { ws.send(JSON.stringify({ action: 'servererrors', data: data })); } catch (ex) { } });
break; break;
} }
@ -4512,6 +4521,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
break; break;
} }
case 'traceinfo': { case 'traceinfo': {
// Only accept is the tracing is allowed for this domain
if ((domain.myserver != null) && (domain.myserver.trace !== true)) break;
if ((user.siteadmin === SITERIGHT_ADMIN) && (typeof command.traceSources == 'object')) { if ((user.siteadmin === SITERIGHT_ADMIN) && (typeof command.traceSources == 'object')) {
parent.parent.debugRemoteSources = command.traceSources; parent.parent.debugRemoteSources = command.traceSources;
parent.parent.DispatchEvent(['*'], obj, { action: 'traceinfo', userid: user._id, username: user.name, traceSources: command.traceSources, nolog: 1, domain: domain.id }); parent.parent.DispatchEvent(['*'], obj, { action: 'traceinfo', userid: user._id, username: user.name, traceSources: command.traceSources, nolog: 1, domain: domain.id });

View File

@ -140,6 +140,14 @@
"_hide": 4, "_hide": 4,
"_footer": "<a href='https://twitter.com/mytwitter'>Twitter</a>", "_footer": "<a href='https://twitter.com/mytwitter'>Twitter</a>",
"_certUrl": "https://192.168.2.106:443/", "_certUrl": "https://192.168.2.106:443/",
"myServer": {
"Backup": false,
"Restore": false,
"Upgrade": false,
"ErrorLog": false,
"Console": false,
"Trace": false
},
"_passwordRequirements": { "_passwordRequirements": {
"min": 8, "min": 8,
"max": 128, "max": 128,

View File

@ -31786,7 +31786,7 @@
"zh-chs": "服务器统计", "zh-chs": "服务器统计",
"zh-cht": "伺服器統計", "zh-cht": "伺服器統計",
"xloc": [ "xloc": [
"default.handlebars->container->column_l->p6->p6info->6" "default.handlebars->container->column_l->p6->p6info->5"
] ]
}, },
{ {

View File

@ -474,8 +474,9 @@
<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> <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>
<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> <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>
</div> </div>
<br />
</div> </div>
<br /><strong>Server Statistics</strong><br /><br /> <strong>Server Statistics</strong><br /><br />
<div id="serverStats"> <div id="serverStats">
<div id="serverCpuChartView" style="display:none"> <div id="serverCpuChartView" style="display:none">
<div class="chartViewCanvas"><canvas id="serverCpuChart"></canvas></div> <div class="chartViewCanvas"><canvas id="serverCpuChart"></canvas></div>
@ -1809,10 +1810,9 @@
} }
function updateSiteAdmin() { function updateSiteAdmin() {
var noServerBackup = '{{{noServerBackup}}}'; var serverFeatures = parseInt('{{{serverfeatures}}}');
var siteRights = userinfo.siteadmin; var siteRights = userinfo.siteadmin;
var accountSettingsLocked = ((siteRights != 0xFFFFFFFF) && ((siteRights & 1024) != 0)); var accountSettingsLocked = ((siteRights != 0xFFFFFFFF) && ((siteRights & 1024) != 0));
if (noServerBackup == 1) { siteRights &= 0xFFFFFFFA; } // If not server backups allowed, remove server backup and restore permissions
// Update account actions // Update account actions
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. 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 @@
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 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
//QV('p2AccountImage', ((features & 4) == 0) && (serverinfo.domainauth == false)); // If account actions are not visible, also remove the image on that panel //QV('p2AccountImage', ((features & 4) == 0) && (serverinfo.domainauth == false)); // If account actions are not visible, also remove the image on that panel
QV('p2AccountImage', !accountSettingsLocked) QV('p2AccountImage', !accountSettingsLocked)
QV('p2ServerActions', siteRights & 21); QV('p2ServerActions', (siteRights & 21) && ((serverFeatures & 15) != 0));
QV('LeftMenuMyServer', siteRights & 21); // 16 + 4 + 1 QV('LeftMenuMyServer', siteRights & 21); // 16 + 4 + 1
QV('MainMenuMyServer', siteRights & 21); QV('MainMenuMyServer', siteRights & 21);
QV('p2ServerActionsBackup', siteRights & 1); QV('p2ServerActionsBackup', (siteRights & 1) && ((serverFeatures & 1) != 0));
QV('p2ServerActionsRestore', siteRights & 4); QV('p2ServerActionsRestore', (siteRights & 4) && ((serverFeatures & 2) != 0));
QV('p2ServerActionsVersion', siteRights & 16); QV('p2ServerActionsVersion', (siteRights & 16) && ((serverFeatures & 4) != 0));
QV('p2ServerActionsErrors', (siteRights & 16) && ((serverFeatures & 8) != 0));
QV('MainMenuMyFiles', siteRights & 8); QV('MainMenuMyFiles', siteRights & 8);
QV('LeftMenuMyFiles', siteRights & 8); QV('LeftMenuMyFiles', siteRights & 8);
if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { setDialogMode(0); go(1); } if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { setDialogMode(0); go(1); }
@ -1847,8 +1848,8 @@
if (xxcurrentView == 4 || ((xxcurrentView >= 30) && (xxcurrentView < 40))) { setDialogMode(0); go(1); currentUser = null; } if (xxcurrentView == 4 || ((xxcurrentView >= 30) && (xxcurrentView < 40))) { setDialogMode(0); go(1); currentUser = null; }
} }
meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) }); meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
QV('ServerConsole', userinfo.siteadmin === 0xFFFFFFFF); QV('ServerConsole', (userinfo.siteadmin === 0xFFFFFFFF) && ((serverFeatures & 16) != 0));
QV('ServerTrace', userinfo.siteadmin === 0xFFFFFFFF); QV('ServerTrace', (userinfo.siteadmin === 0xFFFFFFFF) && ((serverFeatures & 32) != 0));
if ((xxcurrentView == 115) && (userinfo.siteadmin != 0xFFFFFFFF)) { go(6); } if ((xxcurrentView == 115) && (userinfo.siteadmin != 0xFFFFFFFF)) { go(6); }
if ((xxcurrentView == 6) && ((userinfo.siteadmin & 21) == 0)) { go(1); } if ((xxcurrentView == 6) && ((userinfo.siteadmin & 21) == 0)) { go(1); }

View File

@ -2370,8 +2370,41 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
var customui = ''; var customui = '';
if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); } if (domain.customui != null) { customui = encodeURIComponent(JSON.stringify(domain.customui)); }
// Server features
var serverFeatures = 63;
if (domain.myserver) {
if (domain.myserver.backup !== true) { serverFeatures -= 1; } // Allow server backups
if (domain.myserver.restore !== true) { serverFeatures -= 2; } // Allow server restore
if (domain.myserver.upgrade !== true) { serverFeatures -= 4; } // Allow server upgrade
if (domain.myserver.errorlog !== true) { serverFeatures -= 8; } // Allow show server crash log
if (domain.myserver.console !== true) { serverFeatures -= 16; } // Allow server console
if (domain.myserver.trace !== true) { serverFeatures -= 32; } // Allow server tracing
}
// Refresh the session // Refresh the session
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)); 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,
serverfeatures: serverFeatures,
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));
}); });
} else { } else {
// Send back the login application // Send back the login application
@ -4016,7 +4049,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { return; } if (domain == null) { return; }
if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
if ((!req.session) || (req.session == null) || (!req.session.userid) || (obj.parent.args.noserverbackup == 1)) { res.sendStatus(401); return; } if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; }
if ((domain.myserver != null) && (domain.myserver.backup !== true)) { res.sendStatus(401); return; }
var user = obj.users[req.session.userid]; var user = obj.users[req.session.userid];
if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights if ((user == null) || ((user.siteadmin & 1) == 0)) { res.sendStatus(401); return; } // Check if we have server backup rights
@ -4044,7 +4079,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
const domain = checkUserIpAddress(req, res); const domain = checkUserIpAddress(req, res);
if (domain == null) { return; } if (domain == null) { return; }
if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
if (obj.parent.args.noserverbackup == 1) { res.sendStatus(401); return; } if ((domain.myserver != null) && (domain.myserver.restore !== true)) { res.sendStatus(401); return; }
var authUserid = null; var authUserid = null;
if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; } if ((req.session != null) && (typeof req.session.userid == 'string')) { authUserid = req.session.userid; }
const multiparty = require('multiparty'); const multiparty = require('multiparty');
@ -4769,8 +4805,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.app.get(url, handleRootRequest); obj.app.get(url, handleRootRequest);
obj.app.post(url, handleRootPostRequest); obj.app.post(url, handleRootPostRequest);
obj.app.get(url + 'refresh.ashx', function (req, res) { res.sendStatus(200); }); obj.app.get(url + 'refresh.ashx', function (req, res) { res.sendStatus(200); });
obj.app.get(url + 'backup.zip', handleBackupRequest); if ((domain.myserver == null) || (domain.myserver.backup === true)) { obj.app.get(url + 'backup.zip', handleBackupRequest); }
obj.app.post(url + 'restoreserver.ashx', handleRestoreRequest); if ((domain.myserver == null) || (domain.myserver.restore === true)) { obj.app.post(url + 'restoreserver.ashx', handleRestoreRequest); }
obj.app.get(url + 'terms', handleTermsRequest); obj.app.get(url + 'terms', handleTermsRequest);
obj.app.get(url + 'xterm', handleXTermRequest); obj.app.get(url + 'xterm', handleXTermRequest);
obj.app.post(url + 'login', handleLoginRequest); obj.app.post(url + 'login', handleLoginRequest);