From ad2be9afbd13391731f611ee2d208265b52064da Mon Sep 17 00:00:00 2001 From: Felipe Parreira Date: Fri, 11 Sep 2020 07:58:50 -0300 Subject: [PATCH 1/3] test: add e2e tests for REST API user deactivation --- tests/data/rooms.helper.js | 7 +- tests/end-to-end/api/01-users.js | 579 +++++++++++++++++++++++++++++-- 2 files changed, 553 insertions(+), 33 deletions(-) diff --git a/tests/data/rooms.helper.js b/tests/data/rooms.helper.js index 9c995969fbf7..6611f348f31d 100644 --- a/tests/data/rooms.helper.js +++ b/tests/data/rooms.helper.js @@ -1,6 +1,6 @@ import { api, credentials, request } from './api-data'; -export const createRoom = ({ name, type, username }) => { +export const createRoom = ({ name, type, username, members = [] }) => { if (!type) { throw new Error('"type" is required in "createRoom" test helper'); } @@ -18,7 +18,10 @@ export const createRoom = ({ name, type, username }) => { return request.post(api(endpoints[type])) .set(credentials) - .send(params); + .send({ + ...params, + members, + }); }; export const closeRoom = ({ type, roomId }) => { diff --git a/tests/end-to-end/api/01-users.js b/tests/end-to-end/api/01-users.js index dba49e4c0255..3b8ded39906e 100644 --- a/tests/end-to-end/api/01-users.js +++ b/tests/end-to-end/api/01-users.js @@ -18,6 +18,7 @@ import { imgURL } from '../../data/interactions.js'; import { customFieldText, clearCustomFields, setCustomFields } from '../../data/custom-fields.js'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createUser, login, deleteUser, getUserStatus } from '../../data/users.helper.js'; +import { createRoom } from '../../data/rooms.helper'; describe('[Users]', function() { this.retries(0); @@ -1324,6 +1325,165 @@ describe('[Users]', function() { }); }); }); + + it('should return an error when trying to delete user own account if user is the last room owner', async () => { + const user = await createUser(); + const createdUserCredentials = await login(user.username, password); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: user.username, + members: [user.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: user._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('users.deleteOwnAccount')) + .set(createdUserCredentials) + .send({ + password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'), + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', '[user-last-owner]'); + expect(res.body).to.have.property('errorType', 'user-last-owner'); + }); + }); + + it('should delete user own account if the user is the last room owner and `confirmRelinquish` is set to `true`', async () => { + const user = await createUser(); + const createdUserCredentials = await login(user.username, password); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: user.username, + members: [user.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: user._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('users.deleteOwnAccount')) + .set(createdUserCredentials) + .send({ + password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'), + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should assign a new owner to the room if the last room owner is deleted', async () => { + const user = await createUser(); + const createdUserCredentials = await login(user.username, password); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: user.username, + members: [user.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: user._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .post(api('users.deleteOwnAccount')) + .set(createdUserCredentials) + .send({ + password: crypto.createHash('sha256').update(password, 'utf8').digest('hex'), + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.get(api('channels.roles')) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.roles).to.have.lengthOf(1); + expect(res.body.roles[0].roles).to.eql(['owner']); + expect(res.body.roles[0].u).to.have.property('_id', credentials['X-User-Id']); + }); + }); }); describe('[/users.delete]', () => { @@ -1340,7 +1500,7 @@ describe('[Users]', function() { }); const testUsername = `testuserdelete${ +new Date() }`; let targetUser; - it('register a new user...', (done) => { + beforeEach((done) => { request.post(api('users.register')) .set(credentials) .send({ @@ -1353,40 +1513,196 @@ describe('[Users]', function() { .expect(200) .expect((res) => { targetUser = res.body.user; + }).end(done); + }); + + afterEach((done) => { + updatePermission('delete-user', ['admin']).then(() => { + request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + confirmRelinquish: true, + }).end(done); + }); + }); + + it('should return an error when trying delete user account without "delete-user" permission', async () => { + await updatePermission('delete-user', ['user']); + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, }) - .end(done); + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'unauthorized'); + }); }); - it('should return an error when trying delete user account without "delete-user" permission', (done) => { - updatePermission('delete-user', ['user']) - .then(() => { - request.post(api('users.delete')) - .set(credentials) - .send({ - userId: targetUser._id, - }) - .expect('Content-Type', 'application/json') - .expect(403) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('error', 'unauthorized'); - }) - .end(done); + + it('should return an error when trying to delete user account if the user is the last room owner', async () => { + await updatePermission('delete-user', ['admin']); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._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', '[user-last-owner]'); + expect(res.body).to.have.property('errorType', 'user-last-owner'); }); }); - it('should delete user account when logged user has "delete-user" permission', (done) => { - updatePermission('delete-user', ['admin']) - .then(() => { - request.post(api('users.delete')) - .set(credentials) - .send({ - userId: targetUser._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }) - .end(done); + + it('should delete user account if the user is the last room owner and `confirmRelinquish` is set to `true`', async () => { + await updatePermission('delete-user', ['admin']); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should delete user account when logged user has "delete-user" permission', async () => { + await updatePermission('delete-user', ['admin']); + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should assign a new owner to the room if the last room owner is deleted', async () => { + await updatePermission('delete-user', ['admin']); + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.delete')) + .set(credentials) + .send({ + userId: targetUser._id, + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.get(api('channels.roles')) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.roles).to.have.lengthOf(1); + expect(res.body.roles[0].roles).to.eql(['owner']); + expect(res.body.roles[0].u).to.have.property('_id', credentials['X-User-Id']); }); }); }); @@ -1650,6 +1966,207 @@ describe('[Users]', function() { }) .end(done); }); + + it('should return an error when trying to set other user status to inactive and the user is the last owner of a room', async () => { + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: targetUser.username, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.invite')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.setActiveStatus')) + .set(userCredentials) + .send({ + activeStatus: false, + userId: targetUser._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', '[user-last-owner]'); + expect(res.body).to.have.property('errorType', 'user-last-owner'); + }); + }); + + it('should set other user status to inactive if the user is the last owner of a room and `confirmRelinquish` is set to `true`', async () => { + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + username: targetUser.username, + members: [targetUser.username], + })).body.channel; + + await request.post(api('channels.invite')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.setActiveStatus')) + .set(userCredentials) + .send({ + activeStatus: false, + userId: targetUser._id, + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + }); + + it('should set other user as room owner if the last owner of a room is deactivated and `confirmRelinquish` is set to `true`', async () => { + const room = (await createRoom({ + type: 'c', + name: `channel.test.${ Date.now() }-${ Math.random() }`, + members: [targetUser.username], + })).body.channel; + + await request.post(api('users.setActiveStatus')) + .set(userCredentials) + .send({ + activeStatus: true, + userId: targetUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.invite')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.addOwner')) + .set(credentials) + .send({ + userId: targetUser._id, + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('channels.removeOwner')) + .set(credentials) + .send({ + userId: credentials['X-User-Id'], + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.post(api('users.setActiveStatus')) + .set(userCredentials) + .send({ + activeStatus: false, + userId: targetUser._id, + confirmRelinquish: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }); + + await request.get(api('channels.roles')) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.roles).to.have.lengthOf(2); + expect(res.body.roles[1].roles).to.eql(['owner']); + expect(res.body.roles[1].u).to.have.property('_id', credentials['X-User-Id']); + }); + }); + it('should return an error when trying to set other user active status and has not the necessary permission(edit-other-user-active-status)', (done) => { updatePermission('edit-other-user-active-status', []).then(() => { request.post(api('users.setActiveStatus')) From f688d5f70b60c47a8d5d46050e377001e0f634dd Mon Sep 17 00:00:00 2001 From: Felipe Parreira Date: Fri, 11 Sep 2020 07:59:25 -0300 Subject: [PATCH 2/3] fix(app): read confirmRelinquish from HTTP request --- app/api/server/v1/users.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index f08891e7535b..88bafc1b7f72 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -98,8 +98,10 @@ API.v1.addRoute('users.deleteOwnAccount', { authRequired: true }, { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } + const { confirmRelinquish = false } = this.requestParams(); + Meteor.runAsUser(this.userId, () => { - Meteor.call('deleteUserOwnAccount', password); + Meteor.call('deleteUserOwnAccount', password, confirmRelinquish); }); return API.v1.success(); @@ -122,6 +124,8 @@ API.v1.addRoute('users.getAvatar', { authRequired: false }, { API.v1.addRoute('users.setActiveStatus', { authRequired: true }, { post() { + console.log('bodyParams'); + console.log(JSON.stringify(this.bodyParams)); check(this.bodyParams, { userId: String, activeStatus: Boolean, @@ -133,7 +137,8 @@ API.v1.addRoute('users.setActiveStatus', { authRequired: true }, { } Meteor.runAsUser(this.userId, () => { - Meteor.call('setUserActiveStatus', this.bodyParams.userId, this.bodyParams.activeStatus, this.bodyParams.confirmRelinquish); + const { userId, activeStatus, confirmRelinquish = false } = this.bodyParams; + Meteor.call('setUserActiveStatus', userId, activeStatus, confirmRelinquish); }); return API.v1.success({ user: Users.findOneById(this.bodyParams.userId, { fields: { active: 1 } }) }); }, @@ -463,8 +468,10 @@ API.v1.addRoute('users.update', { authRequired: true, twoFactorRequired: true }, } if (typeof this.bodyParams.data.active !== 'undefined') { + const { userId, data: { active }, confirmRelinquish = false } = this.bodyParams; + Meteor.runAsUser(this.userId, () => { - Meteor.call('setUserActiveStatus', this.bodyParams.userId, this.bodyParams.data.active); + Meteor.call('setUserActiveStatus', userId, active, confirmRelinquish); }); } const { fields } = this.parseJsonQuery(); From 126a23c0db9d8fedee87c0f2dbd2249b7a265942 Mon Sep 17 00:00:00 2001 From: Felipe Parreira Date: Fri, 11 Sep 2020 08:09:21 -0300 Subject: [PATCH 3/3] chore(app): remove unnecessary console.log --- app/api/server/v1/users.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index 88bafc1b7f72..789848739e5b 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -124,8 +124,6 @@ API.v1.addRoute('users.getAvatar', { authRequired: false }, { API.v1.addRoute('users.setActiveStatus', { authRequired: true }, { post() { - console.log('bodyParams'); - console.log(JSON.stringify(this.bodyParams)); check(this.bodyParams, { userId: String, activeStatus: Boolean,