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

301 lines
6.2 KiB

import s from 'underscore.string';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import {
hasAllPermission,
hasPermission,
canAccessRoom,
} 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 { searchableRoomTypes } from '../../app/utils/server';
import { readSecondaryPreferred } from '../database/readSecondaryPreferred';
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 = 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: { _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;
}
}