Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove authorization method calls (server) #34986

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/meteor/app/api/server/v1/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { removeUserFromRolesAsync } from '../../../../server/lib/roles/removeUse
import { getUsersInRolePaginated } from '../../../authorization/server/functions/getUsersInRole';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { hasRoleAsync, hasAnyRoleAsync } from '../../../authorization/server/functions/hasRole';
import { addUserToRole } from '../../../authorization/server/methods/addUserToRole';
import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { notifyOnRoleChanged } from '../../../lib/server/lib/notifyListener';
import { settings } from '../../../settings/server/index';
Expand Down Expand Up @@ -81,7 +82,7 @@ API.v1.addRoute(
throw new Meteor.Error('error-user-already-in-role', 'User already in role');
}

await Meteor.callAsync('authorization:addUserToRole', role._id, user.username, roomId);
await addUserToRole(this.userId, role._id, user.username, roomId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-reviewError Handling

try {
  await addUserToRole(this.userId, role._id, user.username, roomId);
} catch (error) {
  throw new Meteor.Error('error-role-assignment-failed', 'Failed to assign user to role', { error: error.message });
}

Multiple functions lack proper error handling, which can lead to unhandled exceptions during role assignments and modifications.

This issue appears in multiple locations:

  • apps/meteor/app/api/server/v1/roles.ts: Lines 85-85
  • apps/meteor/app/authorization/server/methods/addUserToRole.ts: Lines 78-88
  • apps/meteor/app/bot-helpers/server/index.ts: Lines 66-68
  • apps/meteor/app/bot-helpers/server/index.ts: Lines 70-72
  • apps/meteor/app/lib/server/methods/setAdminStatus.ts: Lines 39-43

Please add try-catch blocks around all relevant function calls to handle potential errors gracefully.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


return API.v1.success({
role,
Expand Down
129 changes: 70 additions & 59 deletions apps/meteor/app/authorization/server/methods/addUserToRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,80 +16,91 @@ declare module '@rocket.chat/ddp-client' {
}
}

Meteor.methods<ServerMethods>({
async 'authorization:addUserToRole'(roleId: IRole['_id'], username: IUser['username'], scope) {
const userId = Meteor.userId();
export const addUserToRole = async (userId: string, roleId: string, username: IUser['username'], scope?: string): Promise<boolean> => {
if (!(await hasPermissionAsync(userId, 'access-permissions'))) {
throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', {
method: 'authorization:addUserToRole',
action: 'Accessing_permissions',
});
}

if (!userId || !(await hasPermissionAsync(userId, 'access-permissions'))) {
throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', {
method: 'authorization:addUserToRole',
action: 'Accessing_permissions',
});
}
if (!roleId || typeof roleId.valueOf() !== 'string' || !username || typeof username.valueOf() !== 'string') {
throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', {
method: 'authorization:addUserToRole',
});
}

if (!roleId || typeof roleId.valueOf() !== 'string' || !username || typeof username.valueOf() !== 'string') {
throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', {
method: 'authorization:addUserToRole',
});
}
let role = await Roles.findOneById<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });
if (!role) {
role = await Roles.findOneByName<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });

let role = await Roles.findOneById<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });
if (!role) {
role = await Roles.findOneByName<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });

if (!role) {
throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
method: 'authorization:addUserToRole',
});
}
methodDeprecationLogger.deprecatedParameterUsage(
'authorization:addUserToRole',
'role',
'7.0.0',
({ parameter, method, version }) => `Calling ${method} with \`${parameter}\` names is deprecated and will be removed ${version}`,
);
}

if (role._id === 'admin' && !(await hasPermissionAsync(userId, 'assign-admin-role'))) {
throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', {
throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
method: 'authorization:addUserToRole',
action: 'Assign_admin',
});
}
methodDeprecationLogger.deprecatedParameterUsage(
'authorization:addUserToRole',
'role',
'7.0.0',
({ parameter, method, version }) => `Calling ${method} with \`${parameter}\` names is deprecated and will be removed ${version}`,
);
}

if (role._id === 'admin' && !(await hasPermissionAsync(userId, 'assign-admin-role'))) {
throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', {
method: 'authorization:addUserToRole',
action: 'Assign_admin',
});
}

const user = await Users.findOneByUsernameIgnoringCase(username, {
projection: {
_id: 1,
const user = await Users.findOneByUsernameIgnoringCase(username, {
projection: {
_id: 1,
},
});

if (!user?._id) {
throw new Meteor.Error('error-user-not-found', 'User not found', {
method: 'authorization:addUserToRole',
});
}

// verify if user can be added to given scope
if (scope && !(await Roles.canAddUserToRole(user._id, role._id, scope))) {
throw new Meteor.Error('error-invalid-user', 'User is not part of given room', {
method: 'authorization:addUserToRole',
});
}

const add = await addUserRolesAsync(user._id, [role._id], scope);

if (settings.get('UI_DisplayRoles')) {
void api.broadcast('user.roleUpdate', {
type: 'added',
_id: role._id,
u: {
_id: user._id,
username,
},
scope,
});
}

if (!user?._id) {
throw new Meteor.Error('error-user-not-found', 'User not found', {
method: 'authorization:addUserToRole',
});
}
return add;
};

// verify if user can be added to given scope
if (scope && !(await Roles.canAddUserToRole(user._id, role._id, scope))) {
throw new Meteor.Error('error-invalid-user', 'User is not part of given room', {
method: 'authorization:addUserToRole',
});
}
Meteor.methods<ServerMethods>({
async 'authorization:addUserToRole'(roleId: IRole['_id'], username: IUser['username'], scope) {
const userId = Meteor.userId();

const add = await addUserRolesAsync(user._id, [role._id], scope);

if (settings.get('UI_DisplayRoles')) {
void api.broadcast('user.roleUpdate', {
type: 'added',
_id: role._id,
u: {
_id: user._id,
username,
},
scope,
if (!userId) {
throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', {
method: 'authorization:addUserToRole',
action: 'Accessing_permissions',
});
}

return add;
return addUserToRole(userId, roleId, username, scope);
},
});
135 changes: 73 additions & 62 deletions apps/meteor/app/authorization/server/methods/removeUserFromRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,85 +16,96 @@ declare module '@rocket.chat/ddp-client' {
}
}

Meteor.methods<ServerMethods>({
async 'authorization:removeUserFromRole'(roleId, username, scope) {
const userId = Meteor.userId();
export const removeUserFromRole = async (userId: string, roleId: string, username: IUser['username'], scope?: string): Promise<boolean> => {
if (!(await hasPermissionAsync(userId, 'access-permissions'))) {
throw new Meteor.Error('error-action-not-allowed', 'Access permissions is not allowed', {
method: 'authorization:removeUserFromRole',
action: 'Accessing_permissions',
});
}

if (!userId || !(await hasPermissionAsync(userId, 'access-permissions'))) {
throw new Meteor.Error('error-action-not-allowed', 'Access permissions is not allowed', {
method: 'authorization:removeUserFromRole',
action: 'Accessing_permissions',
});
}
if (!roleId || typeof roleId.valueOf() !== 'string' || !username || typeof username.valueOf() !== 'string') {
throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', {
method: 'authorization:removeUserFromRole',
});
Comment on lines +28 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-reviewError Handling

throw new Meteor.Error('error-invalid-arguments', `Invalid arguments: roleId and username must be non-empty strings`, {

The error message for invalid arguments should be more specific about which argument(s) failed validation to help with debugging and maintainability

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

}

if (!roleId || typeof roleId.valueOf() !== 'string' || !username || typeof username.valueOf() !== 'string') {
throw new Meteor.Error('error-invalid-arguments', 'Invalid arguments', {
let role = await Roles.findOneById<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });
if (!role) {
role = await Roles.findOneByName<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });
if (!role) {
throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
method: 'authorization:removeUserFromRole',
});
}

let role = await Roles.findOneById<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });
if (!role) {
role = await Roles.findOneByName<Pick<IRole, '_id'>>(roleId, { projection: { _id: 1 } });
if (!role) {
throw new Meteor.Error('error-invalid-role', 'Invalid Role', {
method: 'authorization:removeUserFromRole',
});
}
methodDeprecationLogger.deprecatedParameterUsage(
'authorization:removeUserFromRole',
'role',
'7.0.0',
({ parameter, method, version }) => `Calling ${method} with ${parameter} names is deprecated and will be removed ${version}`,
);
}

methodDeprecationLogger.deprecatedParameterUsage(
'authorization:removeUserFromRole',
'role',
'7.0.0',
({ parameter, method, version }) => `Calling ${method} with ${parameter} names is deprecated and will be removed ${version}`,
);
}
const user = await Users.findOneByUsernameIgnoringCase(username, {
projection: {
_id: 1,
roles: 1,
},
});

const user = await Users.findOneByUsernameIgnoringCase(username, {
projection: {
_id: 1,
roles: 1,
if (!user?._id) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'authorization:removeUserFromRole',
});
}

// prevent removing last user from admin role
if (role._id === 'admin') {
const adminCount = await Users.countDocuments({
roles: {
$in: ['admin'],
},
});

if (!user?._id) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'authorization:removeUserFromRole',
const userIsAdmin = user.roles?.indexOf('admin') > -1;
if (adminCount === 1 && userIsAdmin) {
throw new Meteor.Error('error-action-not-allowed', 'Leaving the app without admins is not allowed', {
method: 'removeUserFromRole',
action: 'Remove_last_admin',
});
}
}

// prevent removing last user from admin role
if (role._id === 'admin') {
const adminCount = await Users.countDocuments({
roles: {
$in: ['admin'],
},
});
const remove = await removeUserFromRolesAsync(user._id, [role._id], scope);
const event = {
type: 'removed',
_id: role._id,
u: {
_id: user._id,
username,
},
scope,
} as const;
if (settings.get('UI_DisplayRoles')) {
void api.broadcast('user.roleUpdate', event);
}
void api.broadcast('federation.userRoleChanged', { ...event, givenByUserId: userId });

const userIsAdmin = user.roles?.indexOf('admin') > -1;
if (adminCount === 1 && userIsAdmin) {
throw new Meteor.Error('error-action-not-allowed', 'Leaving the app without admins is not allowed', {
method: 'removeUserFromRole',
action: 'Remove_last_admin',
});
}
}
return remove;
};

const remove = await removeUserFromRolesAsync(user._id, [role._id], scope);
const event = {
type: 'removed',
_id: role._id,
u: {
_id: user._id,
username,
},
scope,
} as const;
if (settings.get('UI_DisplayRoles')) {
void api.broadcast('user.roleUpdate', event);
Meteor.methods<ServerMethods>({
async 'authorization:removeUserFromRole'(roleId, username, scope) {
const userId = Meteor.userId();

if (!userId) {
throw new Meteor.Error('error-action-not-allowed', 'Access permissions is not allowed', {
method: 'authorization:removeUserFromRole',
action: 'Accessing_permissions',
});
}
void api.broadcast('federation.userRoleChanged', { ...event, givenByUserId: userId });

return remove;
return removeUserFromRole(userId, roleId, username, scope);
},
});
12 changes: 7 additions & 5 deletions apps/meteor/app/bot-helpers/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import type { Filter, FindCursor } from 'mongodb';

import { removeUserFromRoomMethod } from '../../../server/methods/removeUserFromRoom';
import { hasRoleAsync } from '../../authorization/server/functions/hasRole';
import { addUserToRole } from '../../authorization/server/methods/addUserToRole';
import { removeUserFromRole } from '../../authorization/server/methods/removeUserFromRole';
import { addUsersToRoomMethod } from '../../lib/server/methods/addUsersToRoom';
import { settings } from '../../settings/server';

Expand Down Expand Up @@ -61,12 +63,12 @@ class BotHelpers {
return p;
}

async addUserToRole(userName: string, roleId: string): Promise<void> {
await Meteor.callAsync('authorization:addUserToRole', roleId, userName);
async addUserToRole(userName: string, roleId: string, userId: string): Promise<void> {
await addUserToRole(userId, roleId, userName);
}

async removeUserFromRole(userName: string, roleId: string): Promise<void> {
await Meteor.callAsync('authorization:removeUserFromRole', roleId, userName);
async removeUserFromRole(userName: string, roleId: string, userId: string): Promise<void> {
await removeUserFromRole(userId, roleId, userName);
}

async addUserToRoom(userName: string, room: string): Promise<void> {
Expand Down Expand Up @@ -205,7 +207,7 @@ Meteor.methods<ServerMethods>({
async botRequest(...args) {
const userID = Meteor.userId();
if (userID && (await hasRoleAsync(userID, 'bot'))) {
return botHelpers.request(...args);
return botHelpers.request(...args, userID);
}
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'botRequest' });
},
Expand Down
8 changes: 6 additions & 2 deletions apps/meteor/app/lib/server/methods/setAdminStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Match, check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';

import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { addUserToRole } from '../../../authorization/server/methods/addUserToRole';
import { removeUserFromRole } from '../../../authorization/server/methods/removeUserFromRole';

declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand Down Expand Up @@ -34,8 +36,10 @@ Meteor.methods<ServerMethods>({
}

if (admin) {
return Meteor.callAsync('authorization:addUserToRole', 'admin', user?.username);
await addUserToRole(uid, 'admin', user?.username);
return;
}
return Meteor.callAsync('authorization:removeUserFromRole', 'admin', user?.username);

await removeUserFromRole(uid, 'admin', user?.username);
},
});
Loading