Skip to content

Commit

Permalink
Add permissions to delete team rooms
Browse files Browse the repository at this point in the history
  • Loading branch information
matheusbsilva137 committed Dec 8, 2023
1 parent 53a9eb9 commit f5b0ac8
Show file tree
Hide file tree
Showing 10 changed files with 53 additions and 19 deletions.
2 changes: 2 additions & 0 deletions apps/meteor/app/authorization/server/constant/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ export const permissions = [
{ _id: 'add-team-channel', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'create-team-channel', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'create-team-group', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'delete-team-channel', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'delete-team-group', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'edit-team-channel', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'remove-team-channel', roles: ['admin', 'owner', 'moderator'] },
{ _id: 'view-all-team-channels', roles: ['admin', 'owner'] },
Expand Down
16 changes: 13 additions & 3 deletions apps/meteor/client/views/hooks/roomActions/useDeleteRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { IRoom, RoomAdminFieldsType } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useRouter, usePermission, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import React from 'react';

import GenericModal from '../../../components/GenericModal';
Expand All @@ -13,13 +13,23 @@ export const useDeleteRoom = (room: IRoom | Pick<IRoom, RoomAdminFieldsType>, {
const router = useRouter();
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const hasPermissionToDelete = usePermission(`delete-${room.t}`, room._id);
const canDeleteRoom = isRoomFederated(room) ? false : hasPermissionToDelete;

const isAdminRoute = router.getRouteName() === 'admin-rooms';

const deleteRoomEndpoint = useEndpoint('POST', '/v1/rooms.delete');
const deleteTeamEndpoint = useEndpoint('POST', '/v1/teams.delete');
const teamsInfoEndpoint = useEndpoint('GET', '/v1/teams.info');

const teamId = room.teamId || '';
const { data: teamInfoData } = useQuery(['teamId', teamId], async () => teamsInfoEndpoint({ teamId }), {
keepPreviousData: true,
retry: false,
enabled: room.teamId !== '',
});

const hasPermissionToDeleteRoom = usePermission(`delete-${room.t}`, room._id);
const hasPermissionToDeleteTeamRoom = usePermission(`delete-team-${room.t === 'c' ? 'channel' : 'group'}`, teamInfoData?.teamInfo.roomId);
const canDeleteRoom = isRoomFederated(room) ? false : hasPermissionToDeleteRoom && hasPermissionToDeleteTeamRoom;

const deleteRoomMutation = useMutation({
mutationFn: deleteRoomEndpoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Row from './Row';
type BaseTeamsChannelsProps = {
loading: boolean;
channels: IRoom[];
mainRoomId: IRoom['_id'];
text: string;
type: 'all' | 'autoJoin';
setType: Dispatch<SetStateAction<'all' | 'autoJoin'>>;
Expand All @@ -39,6 +40,7 @@ type BaseTeamsChannelsProps = {
const BaseTeamsChannels = ({
loading,
channels = [],
mainRoomId,
text,
type,
setText,
Expand Down Expand Up @@ -126,7 +128,9 @@ const BaseTeamsChannels = ({
data={channels}
// eslint-disable-next-line react/no-multi-comp
components={{ Scroller: ScrollableContentWrapper, Footer: () => <InfiniteListAnchor loadMore={loadMoreChannels} /> }}
itemContent={(index, data) => <Row onClickView={onClickView} room={data} reload={reload} key={index} />}
itemContent={(index, data) => (
<Row onClickView={onClickView} room={data} mainRoomId={mainRoomId} reload={reload} key={index} />
)}
/>
</Box>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ const useReactModal = (Component, props) => {
});
};

const RoomActions = ({ room, reload }) => {
const RoomActions = ({ room, mainRoomId, reload }) => {
const t = useTranslation();
const rid = room._id;
const type = room.t;

const dispatchToastMessage = useToastMessageDispatch();

const canDeleteTeamChannel = usePermission(type === 'c' ? 'delete-c' : 'delete-p', rid);
const canDeleteChannel = usePermission(`delete-${type}`, rid);
const canDeleteTeamChannel = usePermission(`delete-team-${type === 'c' ? 'channel' : 'group'}`, mainRoomId);
const canDelete = canDeleteChannel && canDeleteTeamChannel;
const canEditTeamChannel = usePermission('edit-team-channel', rid);
const canRemoveTeamChannel = usePermission('remove-team-channel', rid);

Expand Down Expand Up @@ -107,7 +109,7 @@ const RoomActions = ({ room, reload }) => {
},
action: RemoveFromTeamAction,
},
canDeleteTeamChannel && {
canDelete && {
label: {
label: t('Delete'),
icon: 'trash',
Expand All @@ -125,7 +127,7 @@ const RoomActions = ({ room, reload }) => {
updateRoomEndpoint,
reload,
dispatchToastMessage,
canDeleteTeamChannel,
canDelete,
canRemoveTeamChannel,
canEditTeamChannel,
]);
Expand All @@ -146,7 +148,7 @@ const RoomActions = ({ room, reload }) => {
</Box>
)
}
options={(canEditTeamChannel || canRemoveTeamChannel || canDeleteTeamChannel) && menuOptions}
options={(canEditTeamChannel || canRemoveTeamChannel || canDelete) && menuOptions}
/>
);
};
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/client/views/teams/contextualBar/channels/Row.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React, { memo } from 'react';

import TeamsChannelItem from './TeamsChannelItem';

function Row({ room, onClickView, reload }) {
function Row({ room, mainRoomId, onClickView, reload }) {
if (!room) {
return <TeamsChannelItem.Skeleton />;
}

return <TeamsChannelItem room={room} onClickView={() => onClickView(room)} reload={reload} />;
return <TeamsChannelItem room={room} mainRoomId={mainRoomId} onClickView={() => onClickView(room)} reload={reload} />;
}

export default memo(Row);
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { usePreventPropagation } from '../../../../hooks/usePreventPropagation';
import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator';
import RoomActions from './RoomActions';

const TeamsChannelItem = ({ room, onClickView, reload }) => {
const TeamsChannelItem = ({ room, mainRoomId, onClickView, reload }) => {
const t = useTranslation();
const rid = room._id;
const type = room.t;
Expand All @@ -28,7 +28,9 @@ const TeamsChannelItem = ({ room, onClickView, reload }) => {

const canRemoveTeamChannel = usePermission('remove-team-channel', rid);
const canEditTeamChannel = usePermission('edit-team-channel', rid);
const canDeleteTeamChannel = usePermission(type === 'c' ? 'delete-c' : 'delete-p', rid);
const canDeleteChannel = usePermission(`delete-${type}`, rid);
const canDeleteTeamChannel = usePermission(`delete-team-${type === 'c' ? 'channel' : 'group'}`, mainRoomId);
const canDelete = canDeleteChannel && canDeleteTeamChannel;

const isReduceMotionEnabled = usePrefersReducedMotion();
const handleMenuEvent = {
Expand All @@ -55,9 +57,9 @@ const TeamsChannelItem = ({ room, onClickView, reload }) => {
)}
</Box>
</OptionContent>
{(canRemoveTeamChannel || canEditTeamChannel || canDeleteTeamChannel) && (
{(canRemoveTeamChannel || canEditTeamChannel || canDelete) && (
<OptionMenu onClick={onClick}>
{showButton ? <RoomActions room={room} reload={reload} /> : <IconButton tiny icon='kebab' />}
{showButton ? <RoomActions room={room} reload={reload} mainRoomId={mainRoomId} /> : <IconButton tiny icon='kebab' />}
</OptionMenu>
)}
</Option>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const TeamsChannels = () => {
setType={setType}
setText={handleTextChange}
channels={items}
mainRoomId={room._id}
total={total}
onClickClose={closeTab}
onClickAddExisting={canAddExistingRoomToTeam && addExisting}
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@
"create-team-channel_description": "Permission to create a channel in a team (Overrides global permission)",
"create-team-group": "Create group within team",
"create-team-group_description": "Permission to create a group in a team (Overrides global permission)",
"delete-team-channel": "Delete channel within team",
"delete-team-channel_description": "Permission to delete a channel in a team (complements global permission)",
"delete-team-group": "Delete group within team",
"delete-team-group_description": "Permission to delete a group in a team (complements global permission)",
"add-team-member": "Add Team Member",
"add-team-member_description": "Permission to add members to a team",
"add-user": "Add User",
Expand Down
9 changes: 7 additions & 2 deletions apps/meteor/server/methods/eraseRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export async function eraseRoom(rid: string, uid: string): Promise<void> {
});
}

const team = room.teamId && (await Team.getOneById(room.teamId, { projection: { roomId: 1 } }));
if (team && !(await hasPermissionAsync(uid, `delete-team-${room.t === 'c' ? 'channel' : 'group'}`, team.roomId))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
method: 'eraseRoom',
});
}

if (Apps?.isLoaded()) {
const prevent = await Apps.getBridges()?.getListenerBridge().roomEvent('IPreRoomDeletePrevent', room);
if (prevent) {
Expand All @@ -44,8 +51,6 @@ export async function eraseRoom(rid: string, uid: string): Promise<void> {

await deleteRoom(rid);

const team = room.teamId && (await Team.getOneById(room.teamId));

if (team) {
const user = await Meteor.userAsync();
if (user) {
Expand Down
8 changes: 6 additions & 2 deletions apps/meteor/server/services/team/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,9 +580,13 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
let teamRoomIds: string[];

if (showCanDeleteOnly) {
const canDeleteTeamChannel = await Authorization.hasPermission(userId, 'delete-team-channel', team.roomId);
const canDeleteTeamGroup = await Authorization.hasPermission(userId, 'delete-team-group', team.roomId);
for await (const room of teamRooms) {
const roomType = room.t;
const canDeleteRoom = await Authorization.hasPermission(userId, roomType === 'c' ? 'delete-c' : 'delete-p', room._id);
const isPublicRoom = room.t === 'c';
const canDeleteTeamRoom = isPublicRoom ? canDeleteTeamChannel : canDeleteTeamGroup;
const canDeleteRoom =
canDeleteTeamRoom && (await Authorization.hasPermission(userId, isPublicRoom ? 'delete-c' : 'delete-p', room._id));
room.userCanDelete = canDeleteRoom;
}

Expand Down

0 comments on commit f5b0ac8

Please sign in to comment.