diff --git a/common.js b/common.js index 5a22f73a..c9bae556 100644 --- a/common.js +++ b/common.js @@ -154,7 +154,7 @@ module.exports.validateArray = function (array, minlen, maxlen) { return ((array module.exports.validateStrArray = function (array, minlen, maxlen) { if (((array != null) && Array.isArray(array)) == false) return false; for (var i in array) { if ((typeof array[i] != 'string') && ((minlen == null) || (array[i].length >= minlen)) && ((maxlen == null) || (array[i].length <= maxlen))) return false; } return true; }; module.exports.validateObject = function (obj) { return ((obj != null) && (typeof obj == 'object')); }; module.exports.validateEmail = function (email, minlen, maxlen) { if (module.exports.validateString(email, minlen, maxlen) == false) return false; var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(email); }; -module.exports.validateUsername = function (username, minlen, maxlen) { return (module.exports.validateString(username, minlen, maxlen) && (username.indexOf(' ') == -1)); }; +module.exports.validateUsername = function (username, minlen, maxlen) { return (module.exports.validateString(username, minlen, maxlen) && (username.indexOf(' ') == -1) && (username.indexOf('"') == -1) && (username.indexOf(',') == -1)); }; // Check password requirements module.exports.checkPasswordRequirements = function(password, requirements) { diff --git a/db.js b/db.js index 99e23849..54d5b3ce 100644 --- a/db.js +++ b/db.js @@ -545,6 +545,10 @@ module.exports.CreateDB = function (parent, func) { obj.dispose = function () { for (var x in obj) { if (obj[x].close) { obj[x].close(); } delete obj[x]; } }; obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }).toArray(func); }; obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }).toArray(func); }; + + // TODO: Starting in MongoDB 4.0.3, you should use countDocuments() instead of count() that is deprecated. We should detect MongoDB version and switch. + // https://docs.mongodb.com/manual/reference/method/db.collection.countDocuments/ + //obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.countDocuments({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } } obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } } // Database actions on the events collection diff --git a/meshuser.js b/meshuser.js index e428d572..2df5b8b9 100644 --- a/meshuser.js +++ b/meshuser.js @@ -1478,16 +1478,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use case 'addmeshuser': { if (common.validateString(command.meshid, 1, 1024) == false) break; // Check the meshid - if (common.validateString(command.username, 1, 64) == false) break; // Username is between 1 and 64 characters if (common.validateInt(command.meshadmin) == false) break; // Mesh rights must be an integer - - // Check if the user exists - var newuserid = 'user/' + domain.id + '/' + command.username.toLowerCase(), newuser = parent.users[newuserid]; - if (newuser == null) { - // Send error back, user not found. - displayNotificationMessage('User "' + EscapeHtml(command.username) + '" not found.', 'Device Group', 'ServerNotify'); - break; - } + if (common.validateStrArray(command.usernames, 1, 64) == false) break; // Username is between 1 and 64 characters // Get the mesh mesh = parent.meshes[command.meshid]; @@ -1496,20 +1488,34 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 2) == 0)) return; if ((command.meshid.split('/').length != 3) || (command.meshid.split('/')[1] != domain.id)) return; // Invalid domain, operation only valid for current domain - // Add mesh to user - if (newuser.links == null) newuser.links = {}; - newuser.links[command.meshid] = { rights: command.meshadmin }; - db.SetUser(newuser); - parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe'); + var unknownUsers = []; + for (var i in command.usernames) { + // Check if the user exists + var newuserid = 'user/' + domain.id + '/' + command.usernames[i].toLowerCase(), newuser = parent.users[newuserid]; + if (newuser != null) { + // Add mesh to user + if (newuser.links == null) newuser.links = {}; + newuser.links[command.meshid] = { rights: command.meshadmin }; + db.SetUser(newuser); + parent.parent.DispatchEvent([newuser._id], obj, 'resubscribe'); - // Add a user to the mesh - mesh.links[newuserid] = { userid: newuser.id, name: newuser.name, rights: command.meshadmin }; - db.Set(common.escapeLinksFieldName(mesh)); + // Add a user to the mesh + mesh.links[newuserid] = { userid: newuser.id, name: newuser.name, rights: command.meshadmin }; + db.Set(common.escapeLinksFieldName(mesh)); - // Notify mesh change - var event = { etype: 'mesh', username: newuser.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Added user ' + newuser.name + ' to mesh ' + mesh.name, domain: domain.id }; - if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come. - parent.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, event); + // Notify mesh change + var event = { etype: 'mesh', username: newuser.name, userid: command.userid, meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'meshchange', links: mesh.links, msg: 'Added user ' + newuser.name + ' to mesh ' + mesh.name, domain: domain.id }; + if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the mesh. Another event will come. + parent.parent.DispatchEvent(['*', mesh._id, user._id, newuserid], obj, event); + } else { + unknownUsers.push(command.usernames[i]); + } + } + + if (unknownUsers.length > 0) { + // Send error back, user not found. + displayNotificationMessage('User' + ((unknownUsers.length > 1)?'s':'') + ' ' + EscapeHtml(unknownUsers.join(', ')) + ' not found.', 'Device Group', 'ServerNotify'); + } } break; } diff --git a/package.json b/package.json index 71be302b..554d4283 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.3.5-n", + "version": "0.3.5-o", "keywords": [ "Remote Management", "Intel AMT", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 93c564ad..76f952f2 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 9edd0173..8e1dea3a 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -6183,7 +6183,7 @@ for (var i in sortedusers) { var trash = '', rights = 'Partial Rights', r = sortedusers[i].rights; if (r == 0xFFFFFFFF) rights = 'Full Administrator'; else if (r == 0) rights = 'No Rights'; - if ((i != userinfo._id) && (meshrights == 0xFFFFFFFF || (((meshrights & 2) != 0)))) { trash = ''; } + if ((sortedusers[i].id != userinfo._id) && (meshrights == 0xFFFFFFFF || (((meshrights & 2) != 0)))) { trash = ''; } x += '
 ' + EscapeHtml(decodeURIComponent(sortedusers[i].name)) + '
' + trash + '
' + rights + '
'; ++count; } @@ -6335,10 +6335,10 @@ function p20showAddMeshUserDialog() { if (xxdialogMode) return; - var x = "Allow a user to manage this device group and devices in this group."; + var x = "Allow users to manage this device group and devices in this group."; if (features & 0x00080000) { x += " Users need to login to this server once before they can be added to a device group." } x += "

"; - x += addHtmlValue('User Name', ''); + x += addHtmlValue('User Names', ''); x += '
'; x += 'Full Administrator
'; x += 'Edit Device Group
'; @@ -6355,14 +6355,16 @@ x += 'Wake Devices
'; x += 'Edit Device Notes
'; x += '
'; - setDialogMode(2, "Add User to Device Group", 3, p20showAddMeshUserDialogEx, x); + setDialogMode(2, "Add Users to Device Group", 3, p20showAddMeshUserDialogEx, x); p20validateAddMeshUserDialog(); Q('dp20username').focus(); } function p20validateAddMeshUserDialog() { var meshrights = currentMesh.links[userinfo._id].rights; - QE('idx_dlgOkButton', (Q('dp20username').value.length > 0)); + var ok = true, users = Q('dp20username').value.split(','); + for (var i in users) { var user = users[i].trim(); if (user.length == 0) { ok = false; } else if (user.indexOf('"') >= 0) { ok = false; } } + QE('idx_dlgOkButton', ok); QE('p20fulladmin', meshrights == 0xFFFFFFFF); QE('p20editmesh', (!Q('p20fulladmin').checked) && (meshrights == 0xFFFFFFFF)); QE('p20manageusers', !Q('p20fulladmin').checked); @@ -6396,7 +6398,10 @@ if (Q('p20noamt').checked == true) meshadmin += 2048; if (Q('p20remotelimitedinput').checked == true) meshadmin += 4096; } - meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, username: Q('dp20username').value , meshadmin: meshadmin}); + + var users = Q('dp20username').value.split(','), users2 = []; + for (var i in users) { users2.push(users[i].trim()); } + meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, usernames: users2, meshadmin: meshadmin }); } function p20viewuser(userid) { @@ -6421,9 +6426,9 @@ r = r.substring(2); if (r == '') { r = 'No Rights'; } var uname = userid.split('/')[2]; - if (users) { uname = users[userid].name; } + if (users && users[userid]) { uname = users[userid].name; } if (userinfo._id == userid) { uname = userinfo.name; } - var buttons = 1, x = addHtmlValue('User Name', EscapeHtml(uname)); + var buttons = 1, x = addHtmlValue('User Name', EscapeHtml(decodeURIComponent(uname))); if (userid.split('/')[2] != uname) { x += addHtmlValue('User Identifier', EscapeHtml(userid.split('/')[2])); } x += addHtmlValue('Permissions', r); @@ -6431,9 +6436,15 @@ setDialogMode(2, "Device Group User", buttons, p20viewuserEx, x, userid); } - function p20viewuserEx(button, userid) { if (button != 2) return; setDialogMode(2, "Remote Mesh User", 3, p20viewuserEx2, "Confirm removal of user " + userid.split('/')[2] + "?", userid); } + function p20viewuserEx(button, userid) { + if (button != 2) return; + var uname = userid.split('/')[2]; + if (users && users[userid]) { uname = users[userid].name; } + if (userinfo._id == userid) { uname = userinfo.name; } + setDialogMode(2, "Remote Mesh User", 3, p20viewuserEx2, "Confirm removal of user " + EscapeHtml(decodeURIComponent(uname)) + "?", userid); + } function p20deleteUser(e, userid) { haltEvent(e); p20viewuserEx(2, decodeURIComponent(userid)); } - function p20viewuserEx2(button, userid) { meshserver.send({ action: 'removemeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userid: userid}); } + function p20viewuserEx2(button, userid) { meshserver.send({ action: 'removemeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userid: userid }); } // // MY FILES diff --git a/views/login-min.handlebars b/views/login-min.handlebars index 178df2bf..af98f830 100644 --- a/views/login-min.handlebars +++ b/views/login-min.handlebars @@ -1 +1 @@ - {{{title}}} - Login
{{{title}}}
{{{title2}}}

Welcome


\ No newline at end of file + {{{title}}} - Login
{{{title}}}
{{{title2}}}

Welcome


\ No newline at end of file diff --git a/views/login-mobile-min.handlebars b/views/login-mobile-min.handlebars index caea329c..190add60 100644 --- a/views/login-mobile-min.handlebars +++ b/views/login-mobile-min.handlebars @@ -1 +1 @@ - MeshCentral - Login
{{{title}}}
{{{title2}}}
\ No newline at end of file + MeshCentral - Login
{{{title}}}
{{{title2}}}
\ No newline at end of file diff --git a/views/login-mobile.handlebars b/views/login-mobile.handlebars index ced35c6d..96f0f010 100644 --- a/views/login-mobile.handlebars +++ b/views/login-mobile.handlebars @@ -391,7 +391,7 @@ function validateCreate(box,e) { setDialogMode(0); - var ok = ((Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (validateEmail(Q('aemail').value) == true) && (Q('apassword1').value.length > 0) && (Q('apassword2').value == Q('apassword1').value)); + var ok = ((Q('ausername').value.length > 0) && (Q('ausername').value.indexOf('"') == -1) && (Q('ausername').value.indexOf(',') == -1) && (Q('ausername').value.indexOf(' ') == -1) && (validateEmail(Q('aemail').value) == true) && (Q('apassword1').value.length > 0) && (Q('apassword2').value == Q('apassword1').value)); if ((newAccountPass == 1) && (Q('anewaccountpass').value.length == 0)) { ok = false; } if (Q('apassword1').value == '') { QH('passWarning', ''); diff --git a/views/login.handlebars b/views/login.handlebars index 238a09e0..3e63cc33 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -402,7 +402,7 @@ function validateCreate(box, e) { setDialogMode(0); - var userok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1); + var userok = (Q('ausername').value.length > 0) && (Q('ausername').value.indexOf(' ') == -1) && (Q('ausername').value.indexOf('"') == -1) && (Q('ausername').value.indexOf(',') == -1); var emailok = (validateEmail(Q('aemail').value) == true); var pass1ok = (Q('apassword1').value.length > 0); var pass2ok = (Q('apassword2').value.length > 0) && (Q('apassword2').value == Q('apassword1').value);