From 03f3db2588fc914569f8e2c2200032d6e17694eb Mon Sep 17 00:00:00 2001
From: Tim Kinnane <tim@nestedcode.com>
Date: Thu, 11 Aug 2016 03:27:57 +1000
Subject: [PATCH] Prevent last admin removal (#3971)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* More admin checks

# Conflicts:
#	packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee

* missing server side validation

# Conflicts:
#	packages/rocketchat-ui-flextab/flex-tab/tabs/userEdit.coffee

* Remove last admin check for removeUserFromRoom

* Move and fix last admin check for role removal

Check was on change admin status method, but that didn’t catch direct removal of user from role admin view.

* Fix last admin check for insert/update/delete user

Was checking if only one admin but not if the updated user was admin, preventing all updates.

* Allow translation of last admin error

* Fix assigning admin permission bypass
---
 .../server/methods/addUserToRole.coffee                |  3 +++
 .../server/methods/removeUserFromRole.coffee           |  9 ++++++++-
 packages/rocketchat-lib/i18n/en.i18n.json              |  2 ++
 .../server/methods/insertOrUpdateUser.coffee           | 10 +++++++++-
 server/methods/deleteUser.coffee                       |  6 ++++++
 5 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/packages/rocketchat-authorization/server/methods/addUserToRole.coffee b/packages/rocketchat-authorization/server/methods/addUserToRole.coffee
index bbc03adfd21..771789746a4 100644
--- a/packages/rocketchat-authorization/server/methods/addUserToRole.coffee
+++ b/packages/rocketchat-authorization/server/methods/addUserToRole.coffee
@@ -6,6 +6,9 @@ Meteor.methods
 		if not roleName or not _.isString(roleName) or not username or not _.isString(username)
 			throw new Meteor.Error 'error-invalid-arguments', 'Invalid arguments', { method: 'authorization:addUserToRole' }
 
+		if roleName is 'admin' and not RocketChat.authz.hasPermission Meteor.userId(), 'assign-admin-role'
+			throw new Meteor.Error 'error-action-not-allowed', 'Assigning admin is not allowed', { method: 'insertOrUpdateUser', action: 'Assign_admin' }
+
 		user = RocketChat.models.Users.findOneByUsername username, { fields: { _id: 1 } }
 
 		if not user?._id?
diff --git a/packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee b/packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee
index 4fe7f6f52fa..841754fab84 100644
--- a/packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee
+++ b/packages/rocketchat-authorization/server/methods/removeUserFromRole.coffee
@@ -6,11 +6,18 @@ Meteor.methods
 		if not roleName or not _.isString(roleName) or not username or not _.isString(username)
 			throw new Meteor.Error 'error-invalid-arguments', 'Invalid arguments', { method: 'authorization:removeUserFromRole' }
 
-		user = Meteor.users.findOne { username: username }, { fields: { _id: 1 } }
+		user = Meteor.users.findOne { username: username }, { fields: { _id: 1, roles: 1 } }
 
 		if not user?._id?
 			throw new Meteor.Error 'error-invalid-user', 'Invalid user', { method: 'authorization:removeUserFromRole' }
 
+		# prevent removing last user from admin role
+		if roleName is 'admin'
+			adminCount = Meteor.users.find({ roles: { $in: ['admin'] } }).count()
+			userIsAdmin = user.roles.indexOf('admin') > -1
+			if adminCount is 1 and userIsAdmin
+				throw new Meteor.Error 'error-action-not-allowed', 'Leaving the app without admins is not allowed', { method: 'removeUserFromRole', action: 'Remove_last_admin' }
+
 		remove = RocketChat.models.Roles.removeUserRoles user._id, roleName, scope
 
 		if RocketChat.settings.get('UI_DisplayRoles')
diff --git a/packages/rocketchat-lib/i18n/en.i18n.json b/packages/rocketchat-lib/i18n/en.i18n.json
index 56b8f701828..312156f8f12 100644
--- a/packages/rocketchat-lib/i18n/en.i18n.json
+++ b/packages/rocketchat-lib/i18n/en.i18n.json
@@ -168,6 +168,7 @@
   "are_typing" : "are typing",
   "Are_you_sure" : "Are you sure?",
   "Are_you_sure_you_want_to_delete_your_account" : "Are you sure you want to delete your account?",
+  "Assign_admin" : "Assigning admin",
   "at" : "at",
   "Auth_Token" : "Auth Token",
   "Author" : "Author",
@@ -918,6 +919,7 @@
   "Remove_as_owner" : "Remove as owner",
   "Remove_custom_oauth" : "Remove custom oauth",
   "Remove_from_room" : "Remove from room",
+  "Remove_last_admin" : "Removing last admin",
   "Remove_someone_from_room" : "Remove someone from the room",
   "Removed" : "Removed",
   "Report_Abuse" : "Report Abuse",
diff --git a/packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee b/packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee
index 4895fc2f658..201c6add92a 100644
--- a/packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee
+++ b/packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee
@@ -14,6 +14,9 @@ Meteor.methods
 		if not userData._id and canAddUser isnt true
 			throw new Meteor.Error 'error-action-not-allowed', 'Adding user is not allowd', { method: 'insertOrUpdateUser', action: 'Adding_user' }
 
+		if userData.role is 'admin' and not RocketChat.authz.hasPermission Meteor.userId(), 'assign-admin-role'
+			throw new Meteor.Error 'error-action-not-allowed', 'Assigning admin is not allowed', { method: 'insertOrUpdateUser', action: 'Assign_admin' }
+
 		unless s.trim(userData.name)
 			throw new Meteor.Error 'error-the-field-is-required', 'The field Name is required', { method: 'insertOrUpdateUser', field: 'Name' }
 
@@ -69,7 +72,6 @@ Meteor.methods
 				header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || "")
 				footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || "")
 
-
 				if RocketChat.settings.get('Accounts_UserAddedEmail_Customized')
 					subject = RocketChat.settings.get('Accounts_UserAddedEmailSubject')
 					html = RocketChat.settings.get('Accounts_UserAddedEmail')
@@ -95,10 +97,16 @@ Meteor.methods
 
 			return _id
 		else
+			# prevent removing admin role of last admin
+			adminCount = Meteor.users.find({ roles: { $in: ['admin'] } }).count()
+			if adminCount is 1 and userData.role isnt 'admin'
+				throw new Meteor.Error 'error-action-not-allowed', 'Leaving the app without admins is not allowed', { method: 'insertOrUpdateUser', action: 'Remove_last_admin' }
+
 			#update user
 			updateUser = {
 				$set: {
 					name: userData.name,
+					roles: [ userData.role ],
 					requirePasswordChange: userData.requirePasswordChange
 				}
 			}
diff --git a/server/methods/deleteUser.coffee b/server/methods/deleteUser.coffee
index ec5c304df49..7344baecf15 100644
--- a/server/methods/deleteUser.coffee
+++ b/server/methods/deleteUser.coffee
@@ -12,6 +12,12 @@ Meteor.methods
 		unless user?
 			throw new Meteor.Error 'error-invalid-user', "Invalid user", { method: 'deleteUser' }
 
+		# prevent deleting last admin
+		adminCount = Meteor.users.find({ roles: { $in: ['admin'] } }).count()
+		userIsAdmin = user.roles.indexOf('admin') > -1
+		if adminCount is 1 and userIsAdmin
+			throw new Meteor.Error 'error-action-not-allowed', 'Leaving the app without admins is not allowed', { method: 'deleteUser', action: 'Remove_last_admin' }
+
 		RocketChat.deleteUser(userId)
 
 		return true
-- 
GitLab