[NEW] REST endpoint to delete a DM and allow DM for two other users (#18022)

pull/22990/head
Andrew Bromwich 4 years ago committed by GitHub
parent 5838a7b0d9
commit eda817ac95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      app/api/server/v1/im.js
  2. 2
      app/authorization/server/startup.js
  3. 2
      ee/server/services/.eslintrc.js
  4. 28
      server/methods/createDirectMessage.js
  5. 20
      tests/data/rooms.helper.js
  6. 144
      tests/end-to-end/api/04-direct-message.js

@ -7,8 +7,9 @@ import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMes
import { settings } from '../../../settings/server';
import { API } from '../api';
import { getDirectMessageByNameOrIdWithOptionToJoin } from '../../../lib/server/functions/getDirectMessageByNameOrIdWithOptionToJoin';
import { createDirectMessage } from '../../../../server/methods/createDirectMessage';
function findDirectMessageRoom(params, user) {
function findDirectMessageRoom(params, user, allowAdminOverride) {
if ((!params.roomId || !params.roomId.trim()) && (!params.username || !params.username.trim())) {
throw new Meteor.Error('error-room-param-not-provided', 'Body param "roomId" or "username" is required');
}
@ -18,7 +19,8 @@ function findDirectMessageRoom(params, user) {
nameOrId: params.username || params.roomId,
});
const canAccess = Meteor.call('canAccessRoom', room._id, user._id);
const canAccess = Meteor.call('canAccessRoom', room._id, user._id)
|| (allowAdminOverride && hasPermission(user._id, 'view-room-administration'));
if (!canAccess || !room || room.t !== 'd') {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "username" param provided does not match any direct message');
}
@ -33,7 +35,7 @@ function findDirectMessageRoom(params, user) {
API.v1.addRoute(['dm.create', 'im.create'], { authRequired: true }, {
post() {
const { username, usernames } = this.requestParams();
const { username, usernames, excludeSelf } = this.requestParams();
const users = username ? [username] : usernames && usernames.split(',').map((username) => username.trim());
@ -41,7 +43,7 @@ API.v1.addRoute(['dm.create', 'im.create'], { authRequired: true }, {
throw new Meteor.Error('error-room-not-found', 'The required "username" or "usernames" param provided does not match any direct message');
}
const room = Meteor.call('createDirectMessage', ...users);
const room = createDirectMessage(users, excludeSelf);
return API.v1.success({
room: { ...room, _id: room.rid },
@ -49,6 +51,20 @@ API.v1.addRoute(['dm.create', 'im.create'], { authRequired: true }, {
},
});
API.v1.addRoute(['dm.delete', 'im.delete'], { authRequired: true }, {
post() {
if (!hasPermission(this.userId, 'view-room-administration')) {
return API.v1.unauthorized();
}
const findResult = findDirectMessageRoom(this.requestParams(), this.user, true);
Meteor.call('eraseRoom', findResult.room._id);
return API.v1.success();
},
});
API.v1.addRoute(['dm.close', 'im.close'], { authRequired: true }, {
post() {
const findResult = findDirectMessageRoom(this.requestParams(), this.user);

@ -9,7 +9,7 @@ Meteor.startup(function() {
// Note:
// 1.if we need to create a role that can only edit channel message, but not edit group message
// then we can define edit-<type>-message instead of edit-message
// 2. admin, moderator, and user roles should not be deleted as they are referened in the code.
// 2. admin, moderator, and user roles should not be deleted as they are referenced in the code.
const permissions = [
{ _id: 'access-permissions', roles: ['admin'] },
{ _id: 'access-setting-permissions', roles: ['admin'] },

@ -3,5 +3,5 @@ module.exports = {
extends: '../../../.eslintrc',
rules: {
'import/no-unresolved': 'off',
}
},
};

@ -1,5 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { check, Match } from 'meteor/check';
import { settings } from '../../app/settings';
import { hasPermission } from '../../app/authorization';
@ -8,9 +8,9 @@ import { RateLimiter } from '../../app/lib';
import { addUser } from '../../app/federation/server/functions/addUser';
import { createRoom } from '../../app/lib/server';
Meteor.methods({
createDirectMessage(...usernames) {
export function createDirectMessage(usernames, excludeSelf) {
check(usernames, [String]);
check(excludeSelf, Match.Optional(Boolean));
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
@ -48,12 +48,19 @@ Meteor.methods({
return to;
});
const roomUsers = excludeSelf ? users : [me, ...users];
if (roomUsers.length === 1) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'createDirectMessage',
});
}
if (!hasPermission(Meteor.userId(), 'create-d')) {
// If the user can't create DMs but can access already existing ones
if (hasPermission(Meteor.userId(), 'view-d-room')) {
// Check if the direct room already exists, then return it
const uids = [me, ...users].map(({ _id }) => _id).sort();
const uids = roomUsers.map(({ _id }) => _id).sort();
const room = Rooms.findOneDirectRoomContainingAllUserIDs(uids, { fields: { _id: 1 } });
if (room) {
return {
@ -69,13 +76,22 @@ Meteor.methods({
});
}
const { _id: rid, inserted, ...room } = createRoom('d', null, null, [me, ...users], null, { }, { creator: me._id });
const options = { creator: me._id };
if (excludeSelf && hasPermission(this.userId, 'view-room-administration')) {
options.subscriptionExtra = { open: true };
}
const { _id: rid, inserted, ...room } = createRoom('d', null, null, roomUsers, null, { }, options);
return {
t: 'd',
rid,
...room,
};
}
Meteor.methods({
createDirectMessage(...usernames) {
return createDirectMessage(usernames, false);
},
});

@ -29,24 +29,28 @@ export const asyncCreateRoom = ({ name, type, username, members = [] }) => new P
.end(resolve);
});
export const closeRoom = ({ type, roomId }) => {
function actionRoom({ action, type, roomId }) {
if (!type) {
throw new Error('"type" is required in "closeRoom" test helper');
throw new Error(`"type" is required in "${ action }Room" test helper`);
}
if (!roomId) {
throw new Error('"roomId" is required in "closeRoom" test helper');
throw new Error(`"roomId" is required in "${ action }Room" test helper`);
}
const endpoints = {
c: 'channels.close',
p: 'groups.close',
d: 'im.close',
c: 'channels',
p: 'groups',
d: 'im',
};
return new Promise((resolve) => {
request.post(api(endpoints[type]))
request.post(api(`${ endpoints[type] }.${ action }`))
.set(credentials)
.send({
roomId,
})
.end(resolve);
});
};
}
export const deleteRoom = ({ type, roomId }) => actionRoom({ action: 'delete', type, roomId });
export const closeRoom = ({ type, roomId }) => actionRoom({ action: 'close', type, roomId });

@ -10,6 +10,8 @@ import {
apiEmail,
} from '../../data/api-data.js';
import { password, adminUsername } from '../../data/user.js';
import { deleteRoom } from '../../data/rooms.helper';
import { createUser, deleteUser, login } from '../../data/users.helper';
import { updateSetting, updatePermission } from '../../data/permissions.helper';
@ -209,7 +211,6 @@ describe('[Direct Messages]', function() {
.set(credentials)
.send({
roomId: directMessage._id,
userId: 'rocket.cat',
})
.expect('Content-Type', 'application/json')
.expect(200)
@ -423,6 +424,7 @@ describe('[Direct Messages]', function() {
.end(done);
});
});
describe('/im.members', () => {
it('should return and array with two members', (done) => {
request.get(api('im.members'))
@ -477,4 +479,144 @@ describe('[Direct Messages]', function() {
.end(done);
});
});
describe('/im.create', () => {
let otherUser;
let roomId;
before(async () => {
otherUser = await createUser();
});
after(async () => {
await deleteRoom({ type: 'd', roomId });
await deleteUser(otherUser);
otherUser = undefined;
});
it('creates a DM between two other parties (including self)', (done) => {
request.post(api('im.create'))
.set(credentials)
.send({
usernames: ['rocket.cat', otherUser.username].join(','),
})
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('room').and.to.be.an('object');
expect(res.body.room).to.have.property('usernames').and.to.have.members([adminUsername, 'rocket.cat', otherUser.username]);
roomId = res.body.room._id;
})
.end(done);
});
it('creates a DM between two other parties (excluding self)', (done) => {
request.post(api('im.create'))
.set(credentials)
.send({
usernames: ['rocket.cat', otherUser.username].join(','),
excludeSelf: true,
})
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('room').and.to.be.an('object');
expect(res.body.room).to.have.property('usernames').and.to.have.members(['rocket.cat', otherUser.username]);
roomId = res.body.room._id;
})
.end(done);
});
});
describe('/im.delete', () => {
let testDM;
it('/im.create', (done) => {
request.post(api('im.create'))
.set(credentials)
.send({
username: 'rocket.cat',
})
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
testDM = res.body.room;
})
.end(done);
});
it('/im.delete', (done) => {
request.post(api('im.delete'))
.set(credentials)
.send({
username: 'rocket.cat',
})
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', true);
})
.end(done);
});
it('/im.open', (done) => {
request.post(api('im.open'))
.set(credentials)
.send({
roomId: testDM._id,
})
.expect(400)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('errorType', 'invalid-channel');
})
.end(done);
});
context('when authenticated as a non-admin user', () => {
let otherUser;
let otherCredentials;
before(async () => {
otherUser = await createUser();
otherCredentials = await login(otherUser.username, password);
});
after(async () => {
await deleteUser(otherUser);
otherUser = undefined;
});
it('/im.create', (done) => {
request.post(api('im.create'))
.set(credentials)
.send({
username: otherUser.username,
})
.expect(200)
.expect('Content-Type', 'application/json')
.expect((res) => {
testDM = res.body.room;
})
.end(done);
});
it('/im.delete', (done) => {
request.post(api('im.delete'))
.set(otherCredentials)
.send({
roomId: testDM._id,
})
.expect(403)
.expect('Content-Type', 'application/json')
.expect((res) => {
expect(res.body).to.have.property('success', false);
})
.end(done);
});
});
});
});

Loading…
Cancel
Save