Regression: Add scope to permission checks in Team's endpoints (#21369)

pull/21371/head^2
Matheus Barbosa Silva 4 years ago committed by GitHub
parent 1f606f5ef2
commit 03144c4d85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/api/server/v1/channels.js
  2. 127
      app/api/server/v1/teams.ts
  3. 2
      server/methods/addRoomLeader.js
  4. 2
      server/methods/addRoomModerator.js
  5. 2
      server/methods/addRoomOwner.js
  6. 2
      server/methods/removeRoomLeader.js
  7. 2
      server/methods/removeRoomModerator.js
  8. 2
      server/methods/removeRoomOwner.js
  9. 12
      server/sdk/types/ITeamService.ts
  10. 58
      server/services/team/service.ts

@ -248,7 +248,7 @@ API.v1.addRoute('channels.create', { authRequired: true }, {
const teamMembers = [];
for (const team of teams) {
const { records: members } = Promise.await(Team.members(this.userId, team._id, undefined, canSeeAllTeams, { offset: 0, count: Number.MAX_SAFE_INTEGER }));
const { records: members } = Promise.await(Team.members(this.userId, team._id, canSeeAllTeams, { offset: 0, count: Number.MAX_SAFE_INTEGER }));
const uids = members.map((member) => member.user.username);
teamMembers.push(...uids);
}

@ -68,13 +68,18 @@ API.v1.addRoute('teams.create', { authRequired: true }, {
API.v1.addRoute('teams.addRoom', { authRequired: true }, {
post() {
const { roomId, teamId, isDefault } = this.bodyParams;
const { roomId, teamId, teamName, isDefault } = this.bodyParams;
if (!hasPermission(this.userId, 'add-team-channel')) {
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
if (!hasPermission(this.userId, 'add-team-channel', team.roomId)) {
return API.v1.unauthorized();
}
const room = Promise.await(Team.addRoom(this.userId, roomId, teamId, isDefault));
const room = Promise.await(Team.addRoom(this.userId, roomId, team._id, isDefault));
return API.v1.success({ room });
},
@ -82,13 +87,18 @@ API.v1.addRoute('teams.addRoom', { authRequired: true }, {
API.v1.addRoute('teams.addRooms', { authRequired: true }, {
post() {
const { rooms, teamId } = this.bodyParams;
const { rooms, teamId, teamName } = this.bodyParams;
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
if (!hasPermission(this.userId, 'add-team-channel')) {
if (!hasPermission(this.userId, 'add-team-channel', team.roomId)) {
return API.v1.unauthorized();
}
const validRooms = Promise.await(Team.addRooms(this.userId, rooms, teamId));
const validRooms = Promise.await(Team.addRooms(this.userId, rooms, team._id));
return API.v1.success({ rooms: validRooms });
},
@ -96,15 +106,20 @@ API.v1.addRoute('teams.addRooms', { authRequired: true }, {
API.v1.addRoute('teams.removeRoom', { authRequired: true }, {
post() {
const { roomId, teamId } = this.bodyParams;
const { roomId, teamId, teamName } = this.bodyParams;
if (!hasPermission(this.userId, 'remove-team-channel')) {
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
if (!hasPermission(this.userId, 'remove-team-channel', team.roomId)) {
return API.v1.unauthorized();
}
const canRemoveAny = !!hasPermission(this.userId, 'view-all-team-channels');
const canRemoveAny = !!hasPermission(this.userId, 'view-all-team-channels', team.roomId);
const room = Promise.await(Team.removeRoom(this.userId, roomId, teamId, canRemoveAny));
const room = Promise.await(Team.removeRoom(this.userId, roomId, team._id, canRemoveAny));
return API.v1.success({ room });
},
@ -114,10 +129,12 @@ API.v1.addRoute('teams.updateRoom', { authRequired: true }, {
post() {
const { roomId, isDefault } = this.bodyParams;
if (!hasPermission(this.userId, 'edit-team-channel')) {
const team = Promise.await(Team.getOneByRoomId(roomId));
if (!hasPermission(this.userId, 'edit-team-channel', team.roomId)) {
return API.v1.unauthorized();
}
const canUpdateAny = !!hasPermission(this.userId, 'view-all-team-channels');
const canUpdateAny = !!hasPermission(this.userId, 'view-all-team-channels', team.roomId);
const room = Promise.await(Team.updateRoom(this.userId, roomId, isDefault, canUpdateAny));
@ -127,18 +144,23 @@ API.v1.addRoute('teams.updateRoom', { authRequired: true }, {
API.v1.addRoute('teams.listRooms', { authRequired: true }, {
get() {
const { teamId } = this.queryParams;
const { teamId, teamName } = this.queryParams;
const { offset, count } = this.getPaginationItems();
const { query } = this.parseJsonQuery();
const allowPrivateTeam = hasPermission(this.userId, 'view-all-teams');
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
const allowPrivateTeam = hasPermission(this.userId, 'view-all-teams', team.roomId);
let getAllRooms = false;
if (hasPermission(this.userId, 'view-all-team-channels')) {
if (hasPermission(this.userId, 'view-all-team-channels', team.roomId)) {
getAllRooms = true;
}
const { records, total } = Promise.await(Team.listRooms(this.userId, teamId, getAllRooms, allowPrivateTeam, { offset, count }, { query }));
const { records, total } = Promise.await(Team.listRooms(this.userId, team._id, getAllRooms, allowPrivateTeam, { offset, count }, { query }));
return API.v1.success({
rooms: records,
@ -152,15 +174,20 @@ API.v1.addRoute('teams.listRooms', { authRequired: true }, {
API.v1.addRoute('teams.listRoomsOfUser', { authRequired: true }, {
get() {
const { offset, count } = this.getPaginationItems();
const { teamId, userId } = this.queryParams;
const { teamId, teamName, userId } = this.queryParams;
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
const allowPrivateTeam = hasPermission(this.userId, 'view-all-teams');
const allowPrivateTeam = hasPermission(this.userId, 'view-all-teams', team.roomId);
if (!hasPermission(this.userId, 'view-all-team-channels')) {
if (!hasPermission(this.userId, 'view-all-team-channels', team.roomId)) {
return API.v1.unauthorized();
}
const { records, total } = Promise.await(Team.listRoomsOfUser(this.userId, teamId, userId, allowPrivateTeam, { offset, count }));
const { records, total } = Promise.await(Team.listRoomsOfUser(this.userId, team._id, userId, allowPrivateTeam, { offset, count }));
return API.v1.success({
rooms: records,
@ -176,9 +203,14 @@ API.v1.addRoute('teams.members', { authRequired: true }, {
const { offset, count } = this.getPaginationItems();
const { teamId, teamName } = this.queryParams;
const { query } = this.parseJsonQuery();
const canSeeAllMembers = hasPermission(this.userId, 'view-all-teams');
const { records, total } = Promise.await(Team.members(this.userId, teamId, teamName, canSeeAllMembers, { offset, count }, { query }));
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
const canSeeAllMembers = hasPermission(this.userId, 'view-all-teams', team.roomId);
const { records, total } = Promise.await(Team.members(this.userId, team._id, canSeeAllMembers, { offset, count }, { query }));
return API.v1.success({
members: records,
@ -191,13 +223,18 @@ API.v1.addRoute('teams.members', { authRequired: true }, {
API.v1.addRoute('teams.addMembers', { authRequired: true }, {
post() {
if (!hasAtLeastOnePermission(this.userId, ['add-team-member', 'edit-team-member'])) {
return API.v1.unauthorized();
const { teamId, teamName, members } = this.bodyParams;
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
const { teamId, teamName, members } = this.bodyParams;
if (!hasAtLeastOnePermission(this.userId, ['add-team-member', 'edit-team-member'], team.roomId)) {
return API.v1.unauthorized();
}
Promise.await(Team.addMembers(this.userId, teamId, teamName, members));
Promise.await(Team.addMembers(this.userId, team._id, members));
return API.v1.success();
},
@ -205,13 +242,18 @@ API.v1.addRoute('teams.addMembers', { authRequired: true }, {
API.v1.addRoute('teams.updateMember', { authRequired: true }, {
post() {
if (!hasAtLeastOnePermission(this.userId, ['edit-team-member'])) {
return API.v1.unauthorized();
const { teamId, teamName, member } = this.bodyParams;
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
const { teamId, teamName, member } = this.bodyParams;
if (!hasAtLeastOnePermission(this.userId, ['edit-team-member'], team.roomId)) {
return API.v1.unauthorized();
}
Promise.await(Team.updateMember(teamId, teamName, member));
Promise.await(Team.updateMember(team._id, member));
return API.v1.success();
},
@ -219,13 +261,18 @@ API.v1.addRoute('teams.updateMember', { authRequired: true }, {
API.v1.addRoute('teams.removeMembers', { authRequired: true }, {
post() {
if (!hasAtLeastOnePermission(this.userId, ['edit-team-member'])) {
return API.v1.unauthorized();
const { teamId, teamName, members, rooms } = this.bodyParams;
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
if (!team) {
return API.v1.failure('team-does-not-exist');
}
const { teamId, teamName, members, rooms } = this.bodyParams;
if (!hasAtLeastOnePermission(this.userId, ['edit-team-member'], team.roomId)) {
return API.v1.unauthorized();
}
Promise.await(Team.removeMembers(teamId, teamName, members));
Promise.await(Team.removeMembers(team._id, members));
if (rooms?.length) {
Subscriptions.removeByRoomIdsAndUserId(rooms, this.userId);
@ -239,7 +286,9 @@ API.v1.addRoute('teams.leave', { authRequired: true }, {
post() {
const { teamId, teamName, rooms } = this.bodyParams;
Promise.await(Team.removeMembers(teamId, teamName, [{
const team = teamId ? Promise.await(Team.getOneById(teamId)) : Promise.await(Team.getOneByName(teamName));
Promise.await(Team.removeMembers(team._id, [{
userId: this.userId,
}]));
@ -273,10 +322,6 @@ API.v1.addRoute('teams.info', { authRequired: true }, {
API.v1.addRoute('teams.delete', { authRequired: true }, {
post() {
if (!hasPermission(this.userId, 'delete-team')) {
return API.v1.unauthorized();
}
const { teamId, teamName, roomsToRemove } = this.bodyParams;
if (!teamId && !teamName) {
@ -292,6 +337,10 @@ API.v1.addRoute('teams.delete', { authRequired: true }, {
return API.v1.failure('Team not found.');
}
if (!hasPermission(this.userId, 'delete-team', team.roomId)) {
return API.v1.unauthorized();
}
const rooms: string[] = Promise.await(Team.getMatchingTeamRooms(team._id, roomsToRemove));
// Remove the team's main room

@ -58,7 +58,7 @@ Meteor.methods({
role: 'leader',
});
const team = Promise.await(Team.getOneByRoomId(rid));
const team = Promise.await(Team.getOneByMainRoomId(rid));
if (team) {
Promise.await(Team.addRolesToMember(team._id, userId, ['leader']));
}

@ -58,7 +58,7 @@ Meteor.methods({
role: 'moderator',
});
const team = Promise.await(Team.getOneByRoomId(rid));
const team = Promise.await(Team.getOneByMainRoomId(rid));
if (team) {
Promise.await(Team.addRolesToMember(team._id, userId, ['moderator']));
}

@ -58,7 +58,7 @@ Meteor.methods({
role: 'owner',
});
const team = Promise.await(Team.getOneByRoomId(rid));
const team = Promise.await(Team.getOneByMainRoomId(rid));
if (team) {
Promise.await(Team.addRolesToMember(team._id, userId, ['owner']));
}

@ -58,7 +58,7 @@ Meteor.methods({
role: 'leader',
});
const team = Promise.await(Team.getOneByRoomId(rid));
const team = Promise.await(Team.getOneByMainRoomId(rid));
if (team) {
Promise.await(Team.removeRolesFromMember(team._id, userId, ['leader']));
}

@ -58,7 +58,7 @@ Meteor.methods({
role: 'moderator',
});
const team = Promise.await(Team.getOneByRoomId(rid));
const team = Promise.await(Team.getOneByMainRoomId(rid));
if (team) {
Promise.await(Team.removeRolesFromMember(team._id, userId, ['moderator']));
}

@ -65,7 +65,7 @@ Meteor.methods({
role: 'owner',
});
const team = Promise.await(Team.getOneByRoomId(rid));
const team = Promise.await(Team.getOneByMainRoomId(rid));
if (team) {
Promise.await(Team.removeRolesFromMember(team._id, userId, ['owner']));
}

@ -53,17 +53,19 @@ export interface ITeamService {
listByNames(names: Array<string>, options?: FindOneOptions<ITeam>): Promise<Array<ITeam>>;
listByIds(ids: Array<string>, options?: FindOneOptions<ITeam>): Promise<ITeam[]>;
search(userId: string, term: string | RegExp, options?: FindOneOptions<ITeam>): Promise<ITeam[]>;
members(uid: string, teamId: string, teamName: string, canSeeAll: boolean, options?: IPaginationOptions, queryOptions?: IQueryOptions<ITeamMember>): Promise<IRecordsWithTotal<ITeamMemberInfo>>;
addMembers(uid: string, teamId: string, teamName: string, members: Array<ITeamMemberParams>): Promise<void>;
updateMember(teamId: string, teamName: string, members: ITeamMemberParams): Promise<void>;
removeMembers(teamId: string, teamName: string, members: Array<ITeamMemberParams>): Promise<void>;
members(uid: string, teamId: string, canSeeAll: boolean, options?: IPaginationOptions, queryOptions?: IQueryOptions<ITeamMember>): Promise<IRecordsWithTotal<ITeamMemberInfo>>;
addMembers(uid: string, teamId: string, members: Array<ITeamMemberParams>): Promise<void>;
updateMember(teamId: string, members: ITeamMemberParams): Promise<void>;
removeMembers(teamId: string, members: Array<ITeamMemberParams>): Promise<void>;
getInfoByName(teamName: string): Promise<Partial<ITeam> | undefined>;
getInfoById(teamId: string): Promise<Partial<ITeam> | undefined>;
deleteById(teamId: string): Promise<boolean>;
deleteByName(teamName: string): Promise<boolean>;
unsetTeamIdOfRooms(teamId: string): void;
getOneById(teamId: string, options?: FindOneOptions<ITeam>): Promise<ITeam | undefined>;
getOneByName(teamName: string): Promise<ITeam | null>;
getOneByName(teamName: string, options?: FindOneOptions<ITeam>): Promise<ITeam | null>;
getOneByMainRoomId(teamId: string): Promise<ITeam | null>;
getOneByRoomId(teamId: string): Promise<ITeam | undefined>;
getMatchingTeamRooms(teamId: string, rids: Array<string>): Promise<Array<string>>;
autocomplete(uid: string, name: string): Promise<Array<IRoom>>;
}

@ -476,16 +476,7 @@ export class TeamService extends ServiceClass implements ITeamService {
return rooms.map(({ _id }: { _id: string}) => _id);
}
async members(uid: string, teamId: string, teamName: string, canSeeAll: boolean, { offset, count }: IPaginationOptions = { offset: 0, count: 50 }, { query }: IQueryOptions<ITeam>): Promise<IRecordsWithTotal<ITeamMemberInfo>> {
if (!teamId) {
const teamIdName = await this.TeamModel.findOneByName(teamName, { projection: { _id: 1 } });
if (!teamIdName) {
throw new Error('team-does-not-exist');
}
teamId = teamIdName._id;
}
async members(uid: string, teamId: string, canSeeAll: boolean, { offset, count }: IPaginationOptions = { offset: 0, count: 50 }, { query }: IQueryOptions<ITeam>): Promise<IRecordsWithTotal<ITeamMemberInfo>> {
const isMember = await this.TeamMembersModel.findOneByUserIdAndTeamId(uid, teamId);
if (!isMember && !canSeeAll) {
return {
@ -524,21 +515,12 @@ export class TeamService extends ServiceClass implements ITeamService {
};
}
async addMembers(uid: string, teamId: string, teamName: string, members: Array<ITeamMemberParams>): Promise<void> {
async addMembers(uid: string, teamId: string, members: Array<ITeamMemberParams>): Promise<void> {
const createdBy = await this.Users.findOneById(uid, { projection: { username: 1 } });
if (!createdBy) {
throw new Error('invalid-user');
}
if (!teamId) {
const teamIdName = await this.TeamModel.findOneByName(teamName, { projection: { _id: 1 } });
if (!teamIdName) {
throw new Error('team-does-not-exist');
}
teamId = teamIdName._id;
}
const membersList: Array<Omit<ITeamMember, '_id'>> = members?.map((member) => ({
teamId,
userId: member.userId ? member.userId : '',
@ -552,16 +534,7 @@ export class TeamService extends ServiceClass implements ITeamService {
await this.addMembersToDefaultRooms(createdBy, teamId, membersList);
}
async updateMember(teamId: string, teamName: string, member: ITeamMemberParams): Promise<void> {
if (!teamId) {
const teamIdName = await this.TeamModel.findOneByName(teamName, { projection: { _id: 1 } });
if (!teamIdName) {
throw new Error('team-does-not-exist');
}
teamId = teamIdName._id;
}
async updateMember(teamId: string, member: ITeamMemberParams): Promise<void> {
if (!member.userId) {
member.userId = await this.Users.findOneByUsername(member.userName);
if (!member.userId) {
@ -581,9 +554,8 @@ export class TeamService extends ServiceClass implements ITeamService {
await this.TeamMembersModel.deleteByUserIdAndTeamId(userId, teamId);
}
async removeMembers(teamId: string, teamName: string, members: Array<ITeamMemberParams>): Promise<void> {
const searchTerm = teamId || teamName;
const team = await this.TeamModel[teamId ? 'findOneById' : 'findOneByName'](searchTerm, { projection: { _id: 1, roomId: 1 } });
async removeMembers(teamId: string, members: Array<ITeamMemberParams>): Promise<void> {
const team = await this.TeamModel.findOneById(teamId, { projection: { _id: 1, roomId: 1 } });
if (!team) {
throw new Error('team-does-not-exist');
}
@ -632,14 +604,28 @@ export class TeamService extends ServiceClass implements ITeamService {
return this.TeamModel.findOneById(teamId, options);
}
async getOneByName(teamName: string): Promise<ITeam | null> {
return this.TeamModel.findOneByName(teamName);
async getOneByName(teamName: string, options?: FindOneOptions<ITeam>): Promise<ITeam | null> {
return this.TeamModel.findOneByName(teamName, options);
}
async getOneByRoomId(roomId: string): Promise<ITeam | null> {
async getOneByMainRoomId(roomId: string): Promise<ITeam | null> {
return this.TeamModel.findOneByMainRoomId(roomId, { projection: { _id: 1 } });
}
async getOneByRoomId(roomId: string): Promise<ITeam | undefined> {
const room = await this.RoomsModel.findOneById(roomId);
if (!room) {
throw new Error('invalid-room');
}
if (!room.teamId) {
throw new Error('room-not-on-team');
}
return this.TeamModel.findOneById(room.teamId);
}
async addRolesToMember(teamId: string, userId: string, roles: Array<string>): Promise<boolean> {
const isMember = await this.TeamMembersModel.findOneByUserIdAndTeamId(userId, teamId, { projection: { _id: 1 } });

Loading…
Cancel
Save