The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/server/lib/spotlight.js

235 lines
6.0 KiB

import s from 'underscore.string';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { hasAllPermission, hasPermission, canAccessRoom, roomAccessAttributes } from '../../app/authorization/server';
import { Subscriptions, Rooms } from '../../app/models/server';
import { Users } from '../../app/models/server/raw';
import { settings } from '../../app/settings/server';
import { readSecondaryPreferred } from '../database/readSecondaryPreferred';
import { roomCoordinator } from './rooms/roomCoordinator';
export class Spotlight {
fetchRooms(userId, rooms) {
if (!settings.get('Store_Last_Message') || hasPermission(userId, 'preview-c-room')) {
return rooms;
}
return rooms.map((room) => {
delete room.lastMessage;
return room;
});
}
searchRooms({ userId, text }) {
const regex = new RegExp(s.trim(escapeRegExp(text)), 'i');
const roomOptions = {
limit: 5,
fields: {
t: 1,
name: 1,
joinCodeRequired: 1,
lastMessage: 1,
},
sort: {
name: 1,
},
};
if (userId == null) {
if (!settings.get('Accounts_AllowAnonymousRead')) {
return [];
}
return this.fetchRooms(userId, Rooms.findByNameAndTypeNotDefault(regex, 'c', roomOptions).fetch());
}
if (!hasAllPermission(userId, ['view-outside-room', 'view-c-room'])) {
return [];
}
const searchableRoomTypeIds = roomCoordinator.searchableRoomTypes();
const roomIds = Subscriptions.findByUserIdAndTypes(userId, searchableRoomTypeIds, {
fields: { rid: 1 },
})
.fetch()
.map((s) => s.rid);
const exactRoom = Rooms.findOneByNameAndType(text, searchableRoomTypeIds, roomOptions);
if (exactRoom) {
roomIds.push(exactRoom.rid);
}
return this.fetchRooms(userId, Rooms.findByNameAndTypesNotInIds(regex, searchableRoomTypeIds, roomIds, roomOptions).fetch());
}
mapOutsiders(u) {
u.outside = true;
return u;
}
processLimitAndUsernames(options, usernames, users) {
// Reduce the results from the limit for the next query
options.limit -= users.length;
// If the limit was reached, return
if (options.limit <= 0) {
return users;
}
// Prevent the next query to get the same users
usernames.push(...users.map((u) => u.username).filter((u) => !usernames.includes(u)));
}
_searchInsiderUsers({ rid, text, usernames, options, users, insiderExtraQuery, match = { startsWith: false, endsWith: false } }) {
// Get insiders first
if (rid) {
const searchFields = settings.get('Accounts_SearchFields').trim().split(',');
users.push(
...Promise.await(Users.findByActiveUsersExcept(text, usernames, options, searchFields, insiderExtraQuery, match).toArray()),
);
// If the limit was reached, return
if (this.processLimitAndUsernames(options, usernames, users)) {
return users;
}
}
}
_searchOutsiderUsers({ text, usernames, options, users, canListOutsiders, match = { startsWith: false, endsWith: false } }) {
// Then get the outsiders if allowed
if (canListOutsiders) {
const searchFields = settings.get('Accounts_SearchFields').trim().split(',');
users.push(
...Promise.await(Users.findByActiveUsersExcept(text, usernames, options, searchFields, undefined, match).toArray()).map(
this.mapOutsiders,
),
);
// If the limit was reached, return
if (this.processLimitAndUsernames(options, usernames, users)) {
return users;
}
}
}
_performExtraUserSearches(/* userId, searchParams */) {
// Overwrite this method to include extra searches
}
searchUsers({ userId, rid, text, usernames, mentions }) {
const users = [];
const options = {
limit: settings.get('Number_of_users_autocomplete_suggestions'),
projection: {
username: 1,
nickname: 1,
name: 1,
status: 1,
statusText: 1,
avatarETag: 1,
},
sort: {
[settings.get('UI_Use_Real_Name') ? 'name' : 'username']: 1,
},
readPreference: readSecondaryPreferred(Users.col.s.db),
};
const room = Rooms.findOneById(rid, { fields: { ...roomAccessAttributes, _id: 1, t: 1, uids: 1 } });
if (rid && !room) {
return users;
}
const canListOutsiders = hasAllPermission(userId, ['view-outside-room', 'view-d-room']);
const canListInsiders = canListOutsiders || (rid && canAccessRoom(room, { _id: userId }));
// If can't list outsiders and, wether, the rid was not passed or the user has no access to the room, return
if (!canListOutsiders && !canListInsiders) {
return users;
}
const insiderExtraQuery = [];
if (rid) {
switch (room.t) {
case 'd':
insiderExtraQuery.push({
_id: { $in: room.uids.filter((id) => id !== userId) },
});
break;
case 'l':
insiderExtraQuery.push({
_id: {
$in: Subscriptions.findByRoomId(room._id)
.fetch()
.map((s) => s.u?._id)
.filter((id) => id && id !== userId),
},
});
break;
default:
insiderExtraQuery.push({
__rooms: rid,
});
break;
}
}
const searchParams = {
rid,
text,
usernames,
options,
users,
canListOutsiders,
insiderExtraQuery,
mentions,
};
// Exact match for username only
if (rid) {
const exactMatch = Promise.await(
Users.findOneByUsernameAndRoomIgnoringCase(text, rid, {
projection: options.projection,
readPreference: options.readPreference,
}),
);
if (exactMatch) {
users.push(exactMatch);
this.processLimitAndUsernames(options, usernames, users);
}
}
if (users.length === 0 && canListOutsiders) {
const exactMatch = Promise.await(
Users.findOneByUsernameIgnoringCase(text, {
projection: options.projection,
readPreference: options.readPreference,
}),
);
if (exactMatch) {
users.push(this.mapOutsiders(exactMatch));
this.processLimitAndUsernames(options, usernames, users);
}
}
// Contains for insiders
if (this._searchInsiderUsers(searchParams)) {
return users;
}
// Contains for outsiders
if (this._searchOutsiderUsers(searchParams)) {
return users;
}
if (this._performExtraUserSearches(userId, searchParams)) {
return users;
}
return users;
}
}