From eb6516e76925be9e0f3d03f474d1d32601a46e33 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Wed, 9 Dec 2020 16:34:40 -0800 Subject: [PATCH] Added edituser action to meshctrl.js --- meshctrl.js | 117 +++++++++++++++++++++++++++++++++++++++++----------- meshuser.js | 42 +++++++++++++++++-- 2 files changed, 131 insertions(+), 28 deletions(-) diff --git a/meshctrl.js b/meshctrl.js index d30e2067..2b674fca 100644 --- a/meshctrl.js +++ b/meshctrl.js @@ -7,7 +7,7 @@ try { require('ws'); } catch (ex) { console.log('Missing module "ws", type "npm var settings = {}; const crypto = require('crypto'); const args = require('minimist')(process.argv.slice(2)); -const possibleCommands = ['listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup']; +const possibleCommands = ['edituser', 'listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'editdevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell', 'upload', 'download', 'deviceopenurl', 'devicemessage', 'devicetoast', 'addtousergroup', 'removefromusergroup', 'removeallusersfromusergroup']; if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } } if (args['_'].length == 0) { @@ -27,6 +27,7 @@ if (args['_'].length == 0) { console.log(" DeviceInfo - Show information about a device."); console.log(" Config - Perform operation on config.json file."); console.log(" AddUser - Create a new user account."); + console.log(" EditUser - Change a user account."); console.log(" RemoveUser - Delete a user account."); console.log(" AddUserGroup - Create a new user group."); console.log(" RemoveUserGroup - Delete a user group."); @@ -61,7 +62,7 @@ if (args['_'].length == 0) { console.log(" --loginkey [hex] - Server login key in hex."); console.log(" --loginkeyfile [file] - File containing server login key in hex."); console.log(" --logindomain [domainid] - Domain id, default is empty, only used with loginkey."); - console.log(" --proxy [http://proxy:1] - Specify an HTTP proxy."); + console.log(" --proxy [http://proxy:123] - Specify an HTTP proxy."); return; } else { settings.cmd = args['_'][0].toLowerCase(); @@ -144,6 +145,11 @@ if (args['_'].length == 0) { else { ok = true; } break; } + case 'edituser': { + if (args.userid == null) { console.log("Edit account user missing, use --userid [id]"); } + else { ok = true; } + break; + } case 'removeuser': { if (args.userid == null) { console.log("Remove account userid missing, use --userid [id]"); } else { ok = true; } @@ -366,22 +372,37 @@ if (args['_'].length == 0) { case 'adduser': { console.log("Add a new user account, Example usages:\r\n"); console.log(" MeshCtrl AddUser --user newaccountname --pass newpassword"); + console.log(" MeshCtrl AddUser --user newaccountname --randompass --rights full"); console.log("\r\nRequired arguments:\r\n"); - console.log(" --user [name] - New account name."); - console.log(" --pass [password] - New account password."); - console.log(" --randompass - Create account with a random password."); + console.log(" --user [name] - New account name."); + console.log(" --pass [password] - New account password."); + console.log(" --randompass - Create account with a random password."); console.log("\r\nOptional arguments:\r\n"); - console.log(" --email [email] - New account email address."); - console.log(" --emailverified - New account email is verified."); - console.log(" --resetpass - Request password reset on next login."); - console.log(" --siteadmin - Create the account as full site administrator."); - console.log(" --manageusers - Allow this account to manage server users."); - console.log(" --fileaccess - Allow this account to store server files."); - console.log(" --serverupdate - Allow this account to update the server."); - console.log(" --locked - This account will be locked."); - console.log(" --nonewgroups - Account will not be allowed to create device groups."); - console.log(" --notools - Account not see MeshCMD download links."); - console.log(" --domain [domain] - Account domain, only for cross-domain admins."); + console.log(" --domain [domain] - Account domain, only for cross-domain admins."); + console.log(" --email [email] - New account email address."); + console.log(" --emailverified - New account email is verified."); + console.log(" --resetpass - Request password reset on next login."); + console.log(" --realname [name] - Set the real name for this account."); + console.log(" --phone [number] - Set the account phone number."); + console.log(" --rights [none|full|a,b,c] - Comma seperated list of server permissions. Possible values:"); + console.log(" manageusers,backup,restore,update,fileaccess,locked,nonewgroups,notools,usergroups,recordings,locksettings,allevents"); + break; + } + case 'edituser': { + console.log("Edit a user account, Example usages:\r\n"); + console.log(" MeshCtrl EditUser --userid user --rights locked,locksettings"); + console.log(" MeshCtrl EditUser --userid user --realname Jones"); + console.log("\r\nRequired arguments:\r\n"); + console.log(" --userid [name] - User account identifier."); + console.log("\r\nOptional arguments:\r\n"); + console.log(" --domain [domain] - Account domain, only for cross-domain admins."); + console.log(" --email [email] - Account email address."); + console.log(" --emailverified - Account email is verified."); + console.log(" --resetpass - Request password reset on next login."); + console.log(" --realname [name] - Set the real name for this account."); + console.log(" --phone [number] - Set the account phone number."); + console.log(" --rights [none|full|a,b,c] - Comma seperated list of server permissions. Possible values:"); + console.log(" manageusers,backup,restore,update,fileaccess,locked,nonewgroups,notools,usergroups,recordings,locksettings,allevents"); break; } case 'removeuser': { @@ -982,20 +1003,32 @@ function serverConnect() { break; } case 'adduser': { - var siteadmin = 0; - if (args.siteadmin) { siteadmin = 0xFFFFFFFF; } - if (args.manageusers) { siteadmin |= 2; } - if (args.fileaccess) { siteadmin |= 8; } - if (args.serverupdate) { siteadmin |= 16; } - if (args.locked) { siteadmin |= 32; } - if (args.nonewgroups) { siteadmin |= 64; } - if (args.notools) { siteadmin |= 128; } + var siteadmin = getSiteAdminRights(args); if (args.randompass) { args.pass = getRandomAmtPassword(); } var op = { action: 'adduser', username: args.user, pass: args.pass, responseid: 'meshctrl' }; if (args.email) { op.email = args.email; if (args.emailverified) { op.emailVerified = true; } } if (args.resetpass) { op.resetNextLogin = true; } - if (siteadmin != 0) { op.siteadmin = siteadmin; } + if (siteadmin != -1) { op.siteadmin = siteadmin; } if (args.domain) { op.domain = args.domain; } + if (args.phone === true) { op.phone = ''; } + if (typeof args.phone == 'string') { op.phone = args.phone; } + if (typeof args.realname == 'string') { op.realname = args.realname; } + ws.send(JSON.stringify(op)); + break; + } + case 'edituser': { + var userid = args.userid; + if ((args.domain != null) && (userid.indexOf('/') < 0)) { userid = 'user/' + args.domain + '/' + userid; } + var siteadmin = getSiteAdminRights(args); + var op = { action: 'edituser', userid: userid, responseid: 'meshctrl' }; + if (args.email) { op.email = args.email; if (args.emailverified) { op.emailVerified = true; } } + if (args.resetpass) { op.resetNextLogin = true; } + if (siteadmin != -1) { op.siteadmin = siteadmin; } + if (args.domain) { op.domain = args.domain; } + if (args.phone === true) { op.phone = ''; } + if (typeof args.phone == 'string') { op.phone = args.phone; } + if (typeof args.realname == 'string') { op.realname = args.realname; } + if (args.realname === true) { op.realname = ''; } ws.send(JSON.stringify(op)); break; } @@ -1269,6 +1302,39 @@ function serverConnect() { } }); + function getSiteAdminRights(args) { + var siteadmin = -1; + if (typeof args.rights == 'number') { + siteadmin = args.rights; + } else if (typeof args.rights == 'string') { + siteadmin = 0; + var srights = args.rights.toLowerCase().split(','); + if (srights.indexOf('full') != -1) { siteadmin = 0xFFFFFFFF; } + if (srights.indexOf('none') != -1) { siteadmin = 0x00000000; } + if (srights.indexOf('backup') != -1) { siteadmin |= 0x00000001; } + if (srights.indexOf('manageusers') != -1) { siteadmin |= 0x00000002; } + if (srights.indexOf('restore') != -1) { siteadmin |= 0x00000004; } + if (srights.indexOf('fileaccess') != -1) { siteadmin |= 0x00000008; } + if (srights.indexOf('update') != -1) { siteadmin |= 0x00000010; } + if (srights.indexOf('locked') != -1) { siteadmin |= 0x00000020; } + if (srights.indexOf('nonewgroups') != -1) { siteadmin |= 0x00000040; } + if (srights.indexOf('notools') != -1) { siteadmin |= 0x00000080; } + if (srights.indexOf('usergroups') != -1) { siteadmin |= 0x00000100; } + if (srights.indexOf('recordings') != -1) { siteadmin |= 0x00000200; } + if (srights.indexOf('locksettings') != -1) { siteadmin |= 0x00000400; } + if (srights.indexOf('allevents') != -1) { siteadmin |= 0x00000800; } + } + + if (args.siteadmin) { siteadmin = 0xFFFFFFFF; } + if (args.manageusers) { if (siteadmin == -1) { siteadmin = 0; } siteadmin |= 2; } + if (args.fileaccess) { if (siteadmin == -1) { siteadmin = 0; } siteadmin |= 8; } + if (args.serverupdate) { if (siteadmin == -1) { siteadmin = 0; } siteadmin |= 16; } + if (args.locked) { if (siteadmin == -1) { siteadmin = 0; } siteadmin |= 32; } + if (args.nonewgroups) { if (siteadmin == -1) { siteadmin = 0; } siteadmin |= 64; } + if (args.notools) { if (siteadmin == -1) { siteadmin = 0; } siteadmin |= 128; } + return siteadmin; + } + ws.on('close', function() { process.exit(); }); ws.on('error', function (err) { if (err.code == 'ENOTFOUND') { console.log('Unable to resolve ' + url); } @@ -1360,6 +1426,7 @@ function serverConnect() { case 'msg': // SHELL case 'toast': // TOAST case 'adduser': // ADDUSER + case 'edituser': // EDITUSER case 'deleteuser': // REMOVEUSER case 'createmesh': // ADDDEVICEGROUP case 'deletemesh': // REMOVEDEVICEGROUP diff --git a/meshuser.js b/meshuser.js index ccb4dcd9..4dfcc3c3 100644 --- a/meshuser.js +++ b/meshuser.js @@ -1931,6 +1931,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (command.email != null) { newuser.email = command.email.toLowerCase(); if (command.emailVerified === true) { newuser.emailVerified = true; } } // Email if (command.resetNextLogin === true) { newuser.passchange = -1; } else { newuser.passchange = Math.floor(Date.now() / 1000); } if (user.groups) { newuser.groups = user.groups; } // New accounts are automatically part of our groups (Realms). + if (common.validateString(command.realname, 1, 256)) { newuser.realname = command.realname; } + if ((command.consent != null) && (typeof command.consent == 'number')) { if (command.consent == 0) { delete chguser.consent; } else { newuser.consent = command.consent; } change = 1; } + if ((command.phone != null) && (typeof command.phone == 'string') && ((command.phone == '') || isPhoneNumber(command.phone))) { if (command.phone == '') { delete newuser.phone; } else { newuser.phone = command.phone; } change = 1; } // Auto-join any user groups if (typeof newuserdomain.newaccountsusergroups == 'object') { @@ -1997,8 +2000,37 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Must be user administrator or edit self. if (((user.siteadmin & 2) == 0) && (user._id != command.id)) break; + // User the username as userid if needed + if ((typeof command.username == 'string') && (command.userid == null)) { command.userid = command.username; } + if ((typeof command.id == 'string') && (command.userid == null)) { command.userid = command.id; } + + // Edit a user account + var err = null, editusersplit, edituserid, edituser, edituserdomain; + try { + if ((user.siteadmin & 2) == 0) { err = 'Permission denied'; } + else if (common.validateString(command.userid, 1, 2048) == false) { err = 'Invalid userid'; } + else { + if (command.userid.indexOf('/') < 0) { command.userid = 'user/' + domain.id + '/' + command.userid; } + editusersplit = command.userid.split('/'); + edituserid = command.userid; + edituser = parent.users[edituserid]; + if (edituser == null) { err = 'User does not exists'; } + else if ((obj.crossDomain !== true) && ((editusersplit.length != 3) || (editusersplit[1] != domain.id))) { err = 'Invalid domain'; } // Invalid domain, operation only valid for current domain + else if ((edituser.siteadmin === SITERIGHT_ADMIN) && (user.siteadmin != SITERIGHT_ADMIN)) { err = 'Permission denied'; } // Need full admin to remote another administrator + else if ((obj.crossDomain !== true) && (user.groups != null) && (user.groups.length > 0) && ((edituser.groups == null) || (findOne(edituser.groups, user.groups) == false))) { err = 'Invalid user group'; } // Can only perform this operation on other users of our group. + } + } catch (ex) { err = 'Validation exception: ' + ex; } + + // Handle any errors + if (err != null) { + if (command.responseid != null) { + try { ws.send(JSON.stringify({ action: 'edituser', responseid: command.responseid, result: err })); } catch (ex) { } + } + break; + } + // Edit a user account, may involve changing email or administrator permissions - var chguser = parent.users[command.id]; + var chguser = parent.users[edituserid]; change = 0; if (chguser) { // If the target user is admin and we are not admin, no changes can be made. @@ -2010,7 +2042,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } // Fetch and validate the user domain - var edituserdomainid = command.id.split('/')[1]; + var edituserdomainid = edituserid.split('/')[1]; if ((obj.crossDomain !== true) && (edituserdomainid != domain.id)) break; var edituserdomain = parent.parent.config.domains[edituserdomainid]; if (edituserdomain == null) break; @@ -2023,7 +2055,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } } - // Validate and change realm name + // Validate and change real name if (common.validateString(command.realname, 0, 256) && (chguser.realname != command.realname)) { if (command.realname == '') { delete chguser.realname; } else { chguser.realname = command.realname; } change = 1; @@ -2032,6 +2064,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Make changes if ((command.emailVerified === true || command.emailVerified === false) && (chguser.emailVerified != command.emailVerified)) { chguser.emailVerified = command.emailVerified; change = 1; } if ((common.validateInt(command.quota, 0) || command.quota == null) && (command.quota != chguser.quota)) { chguser.quota = command.quota; if (chguser.quota == null) { delete chguser.quota; } change = 1; } + if (command.resetNextLogin === true) { chguser.passchange = -1; } if ((command.consent != null) && (typeof command.consent == 'number')) { if (command.consent == 0) { delete chguser.consent; } else { chguser.consent = command.consent; } change = 1; } 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; } @@ -2089,6 +2122,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use parent.parent.DispatchEvent([chguser._id], obj, 'close'); // Disconnect all this user's sessions } } + + // OK Response + if (command.responseid != null) { try { ws.send(JSON.stringify({ action: 'edituser', responseid: command.responseid, result: 'ok' })); } catch (ex) { } } break; } case 'usergroups':