diff --git a/app/api/server/lib/rooms.js b/app/api/server/lib/rooms.js index 9bf407ae23969..99aef384ed779 100644 --- a/app/api/server/lib/rooms.js +++ b/app/api/server/lib/rooms.js @@ -136,11 +136,11 @@ export async function findRoomsAvailableForTeams({ uid, name }) { }, }; - const userRooms = Subscriptions.findByUserIdAndType(uid, 'p', { fields: { rid: 1 } }) + const userRooms = Subscriptions.findByUserIdAndRoles(uid, ['owner'], { fields: { rid: 1 } }) .fetch() .map((item) => item.rid); - const rooms = await Rooms.findChannelAndGroupListWithoutTeamsByNameStarting(name, userRooms, options).toArray(); + const rooms = await Rooms.findChannelAndGroupListWithoutTeamsByNameStartingByOwner(uid, name, userRooms, options).toArray(); return { items: rooms, diff --git a/app/api/server/v1/teams.ts b/app/api/server/v1/teams.ts index 0a05640a5e194..fb972a4d219ad 100644 --- a/app/api/server/v1/teams.ts +++ b/app/api/server/v1/teams.ts @@ -78,7 +78,7 @@ API.v1.addRoute('teams.addRooms', { authRequired: true }, { } if (!hasPermission(this.userId, 'add-team-channel', team.roomId)) { - return API.v1.unauthorized(); + return API.v1.unauthorized('error-no-permission-team-channel'); } const validRooms = Promise.await(Team.addRooms(this.userId, rooms, team._id)); diff --git a/app/models/server/models/Subscriptions.js b/app/models/server/models/Subscriptions.js index c6e29179c86d5..695212ef243a5 100644 --- a/app/models/server/models/Subscriptions.js +++ b/app/models/server/models/Subscriptions.js @@ -468,6 +468,15 @@ export class Subscriptions extends Base { return this.find(query, options); } + findByUserIdAndRoles(userId, roles, options) { + const query = { + 'u._id': userId, + roles: { $in: roles }, + }; + + return this.find(query, options); + } + findByUserIdUpdatedAfter(userId, updatedAt, options) { const query = { 'u._id': userId, diff --git a/app/models/server/raw/Rooms.js b/app/models/server/raw/Rooms.js index 7de9db6b38b20..b54a126c5b5c7 100644 --- a/app/models/server/raw/Rooms.js +++ b/app/models/server/raw/Rooms.js @@ -182,7 +182,7 @@ export class RoomsRaw extends BaseRaw { return this.find(query, options); } - findChannelAndGroupListWithoutTeamsByNameStarting(name, groupsToAccept, options) { + findChannelAndGroupListWithoutTeamsByNameStartingByOwner(uid, name, groupsToAccept, options) { const nameRegex = new RegExp(`^${ escapeRegExp(name).trim() }`, 'i'); const query = { @@ -192,20 +192,11 @@ export class RoomsRaw extends BaseRaw { prid: { $exists: false, }, - $or: [ - { - t: 'c', - }, - { - t: 'p', - _id: { - $in: groupsToAccept, - }, - }, - ], + _id: { + $in: groupsToAccept, + }, name: nameRegex, }; - return this.find(query, options); } diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index a0b5723fd4418..60e570a76dc17 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1705,6 +1705,8 @@ "error-user-registration-disabled": "User registration is disabled", "error-user-registration-secret": "User registration is only allowed via Secret URL", "error-validating-department-chat-closing-tags": "At least one closing tag is required when the department requires tag(s) on closing conversations.", + "error-no-permission-team-channel": "You don't have permission to add this channel to the team", + "error-no-owner-channel": "Only owners can add this channel to the team", "error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.", "Errors_and_Warnings": "Errors and Warnings", "Esc_to": "Esc to", diff --git a/server/services/team/service.ts b/server/services/team/service.ts index a19bb27fdd017..3ba6ee1c2d3f3 100644 --- a/server/services/team/service.ts +++ b/server/services/team/service.ts @@ -312,20 +312,20 @@ export class TeamService extends ServiceClass implements ITeamService { } // validate access for every room first - validRooms.forEach(async (room) => { + for await (const room of validRooms) { const canSeeRoom = await canAccessRoom(room, user); if (!canSeeRoom) { throw new Error('invalid-room'); } - }); + } - for (const room of validRooms) { + for await (const room of validRooms) { if (room.teamId) { throw new Error('room-already-on-team'); } - if (room.u?._id !== uid) { - throw new Error('invalid-user'); + if (!await this.SubscriptionsModel.isUserInRole(uid, 'owner', room._id)) { + throw new Error('error-no-owner-channel'); } room.teamId = teamId; diff --git a/tests/end-to-end/api/25-teams.js b/tests/end-to-end/api/25-teams.js index 9449d30afa712..c4ecd1b168398 100644 --- a/tests/end-to-end/api/25-teams.js +++ b/tests/end-to-end/api/25-teams.js @@ -1,9 +1,9 @@ import { expect } from 'chai'; -import { getCredentials, api, request, credentials } from '../../data/api-data'; +import { getCredentials, api, request, credentials, methodCall } from '../../data/api-data'; import { updatePermission } from '../../data/permissions.helper.js'; -describe('[Teams]', () => { +describe.only('[Teams]', () => { before((done) => getCredentials(done)); const community = `community${ Date.now() }`; @@ -15,13 +15,14 @@ describe('[Teams]', () => { let privateRoom2 = null; let testUser; let testUser2; + const testUserCredentials = {}; before('Create test users', (done) => { let username = `user.test.${ Date.now() }`; let email = `${ username }@rocket.chat`; request.post(api('users.create')) .set(credentials) - .send({ email, name: username, username, password: username }) + .send({ email, name: username, username, password: username, roles: ['user'] }) .then((res) => { testUser = res.body.user; @@ -37,6 +38,21 @@ describe('[Teams]', () => { }); }); + before('login testUser', (done) => { + request.post(api('login')) + .send({ + user: testUser.username, + password: testUser.username, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + testUserCredentials['X-Auth-Token'] = res.body.data.authToken; + testUserCredentials['X-User-Id'] = res.body.data.userId; + }) + .end(done); + }); + describe('/teams.create', () => { it('should create a public team', (done) => { request.post(api('teams.create')) @@ -654,7 +670,8 @@ describe('[Teams]', () => { expect(res.body.rooms[0]).to.have.property('teamId', teamId); expect(res.body.rooms[0]).to.not.have.property('teamDefault'); }) - .then(() => done()); + .then(() => done()) + .catch(done); }); before('create channel 2', (done) => { @@ -742,6 +759,8 @@ describe('[Teams]', () => { }); describe('/teams.addRooms', () => { + let privateRoom3; + before('create private channel', (done) => { const channelName = `community-channel-private${ Date.now() }`; request.post(api('groups.create')) @@ -780,6 +799,25 @@ describe('[Teams]', () => { }) .end(done); }); + before('create yet another private channel', (done) => { + const channelName = `community-channel-private${ Date.now() }`; + request.post(api('groups.create')) + .set(credentials) + .send({ + name: channelName, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.nested.property('group._id'); + expect(res.body).to.have.nested.property('group.name', channelName); + expect(res.body).to.have.nested.property('group.t', 'p'); + expect(res.body).to.have.nested.property('group.msgs', 0); + privateRoom3 = res.body.group; + }) + .end(done); + }); before('create public channel', (done) => { const channelName = `community-channel-public${ Date.now() }`; request.post(api('channels.create')) @@ -832,7 +870,7 @@ describe('[Teams]', () => { .expect((res) => { expect(res.body).to.have.property('success', false); expect(res.body).to.have.property('error'); - expect(res.body.error).to.be.equal('unauthorized'); + expect(res.body.error).to.be.equal('error-no-permission-team-channel'); }) .end(done); }); @@ -905,6 +943,60 @@ describe('[Teams]', () => { .end(done); }); }); + + it('should fail if the user cannot access the channel', (done) => { + updatePermission('add-team-channel', ['admin', 'user']) + .then(() => { + request.post(api('teams.addRooms')) + .set(testUserCredentials) + .send({ + rooms: [privateRoom3._id], + teamId: privateTeam._id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('invalid-room'); + }) + .end(done); + }) + .catch(done); + }); + + it('should fail if the user is not the owner of the channel', (done) => { + request.post(methodCall('addUsersToRoom')) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'addUsersToRoom', + params: [{ rid: privateRoom3._id, users: [testUser.username] }], + }), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .then(() => { + request.post(api('teams.addRooms')) + .set(testUserCredentials) + .send({ + rooms: [privateRoom3._id], + teamId: privateTeam._id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error'); + expect(res.body.error).to.be.equal('error-no-owner-channel'); + }) + .end(done); + }) + .catch(done); + }); }); describe('/teams.listRooms', () => {