diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index dbc1bd418c3be..92322ba70fa88 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi8/nodejs-12 -ENV RC_VERSION 3.11.1 +ENV RC_VERSION 3.11.6 MAINTAINER buildmaster@rocket.chat diff --git a/.github/history-manual.json b/.github/history-manual.json index 87583c83bfa9f..c7d38f433d47d 100644 --- a/.github/history-manual.json +++ b/.github/history-manual.json @@ -56,5 +56,37 @@ "contributors": [ "sampaiodiego" ] + }], + "3.11.2": [{ + "title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }], + "3.11.3": [{ + "title": "[FIX] Bump Livechat widget", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, { + "title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "g-thome", + "KevLehman", + "matheusbsilva137" + ] + }], + "3.11.4": [{ + "title": "[FIX] Security Hotfix (https://docs.rocket.chat/guides/security/security-updates)", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "KevLehman", + "renatobecker" + ] }] } diff --git a/.github/history.json b/.github/history.json index aa889cac99312..3fdddbf40f9c2 100644 --- a/.github/history.json +++ b/.github/history.json @@ -55308,6 +55308,152 @@ ] } ] + }, + "3.11.2": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "20727", + "title": "[FIX] Room owner not being able to override global retention policy", + "userLogin": "g-thome", + "description": "use correct permissions to check if room owner can override global retention policy", + "milestone": "3.11.2", + "contributors": [ + "g-thome" + ] + }, + { + "pr": "20860", + "title": "[FIX] Prevent Message Attachment rendering", + "userLogin": "ggazzo", + "milestone": "3.11.2", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "20740", + "title": "[FIX] External systems not being able to change Omnichannel Inquiry priorities ", + "userLogin": "renatobecker", + "description": "Due to a wrong property name, external applications were not able to change the priority of Omnichannel Inquires.", + "milestone": "3.11.2", + "contributors": [ + "renatobecker" + ] + } + ] + }, + "3.10.6": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.11.3": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.8.9": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.19.0", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.10.7": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.21.0-alpha.4235", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.11.4": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "3.11.5": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "21644", + "title": "[FIX] Livechat not retrieving messages", + "userLogin": "cuonghuunguyen", + "milestone": "3.13.3", + "contributors": [ + "cuonghuunguyen" + ] + } + ] + }, + "3.11.6": { + "node_version": "12.18.4", + "npm_version": "6.14.8", + "apps_engine_version": "1.22.2", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "22927", + "title": "[FIX] User presence being processes even if presence monitor was disabled", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "22257", + "title": "[FIX] Support DISABLE_PRESENCE_MONITOR env var in new DB watchers", + "userLogin": "sampaiodiego", + "milestone": "3.14.5", + "contributors": [ + "sampaiodiego" + ] + } + ] } } } \ No newline at end of file diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 27c376f35f62c..fe8fdb71df652 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -381,6 +381,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: 'us-east-1' GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} REDHAT_REGISTRY_PID: ${{ secrets.REDHAT_REGISTRY_PID }} REDHAT_REGISTRY_KEY: ${{ secrets.REDHAT_REGISTRY_KEY }} diff --git a/.snapcraft/resources/prepareRocketChat b/.snapcraft/resources/prepareRocketChat index 8388f031d5c5c..9566cfdd7ed38 100755 --- a/.snapcraft/resources/prepareRocketChat +++ b/.snapcraft/resources/prepareRocketChat @@ -1,6 +1,6 @@ #!/bin/bash -curl -SLf "https://releases.rocket.chat/3.11.1/download/" -o rocket.chat.tgz +curl -SLf "https://releases.rocket.chat/3.11.6/download/" -o rocket.chat.tgz tar xf rocket.chat.tgz --strip 1 diff --git a/.snapcraft/snap/snapcraft.yaml b/.snapcraft/snap/snapcraft.yaml index 19e9d98c5f81e..a3dc3e90cc07d 100644 --- a/.snapcraft/snap/snapcraft.yaml +++ b/.snapcraft/snap/snapcraft.yaml @@ -7,7 +7,7 @@ # 5. `snapcraft snap` name: rocketchat-server -version: 3.11.1 +version: 3.11.6 summary: Rocket.Chat server description: Have your own Slack like online chat, built with Meteor. https://rocket.chat/ confinement: strict diff --git a/HISTORY.md b/HISTORY.md index ae6f7fccf5320..c2ebdba2e45ea 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,116 @@ +# 3.11.6 +`2022-08-22 ยท 2 ๐Ÿ› ยท 1 ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.22.2` + +### ๐Ÿ› Bug fixes + + +- Support DISABLE_PRESENCE_MONITOR env var in new DB watchers ([#22257](https://github.com/RocketChat/Rocket.Chat/pull/22257)) + +- User presence being processes even if presence monitor was disabled ([#22927](https://github.com/RocketChat/Rocket.Chat/pull/22927)) + +### ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป Core Team ๐Ÿค“ + +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.11.5 +`2021-04-20 ยท 1 ๐Ÿ› ยท 1 ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.22.2` + +### ๐Ÿ› Bug fixes + + +- Livechat not retrieving messages ([#21644](https://github.com/RocketChat/Rocket.Chat/pull/21644) by [@cuonghuunguyen](https://github.com/cuonghuunguyen)) + +### ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป Contributors ๐Ÿ˜ + +- [@cuonghuunguyen](https://github.com/cuonghuunguyen) + +# 3.11.4 +`2021-04-14 ยท 1 ๐Ÿ› ยท 3 ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.22.2` + +### ๐Ÿ› Bug fixes + + +- Security Hotfix (https://docs.rocket.chat/guides/security/security-updates) + +### ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป Core Team ๐Ÿค“ + +- [@KevLehman](https://github.com/KevLehman) +- [@renatobecker](https://github.com/renatobecker) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.11.3 +`2021-03-26 ยท 2 ๐Ÿ› ยท 4 ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.22.2` + +### ๐Ÿ› Bug fixes + + +- Bump Livechat widget + +- Security Hotfix (https://docs.rocket.chat/guides/security/security-updates) + +### ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป Core Team ๐Ÿค“ + +- [@KevLehman](https://github.com/KevLehman) +- [@g-thome](https://github.com/g-thome) +- [@matheusbsilva137](https://github.com/matheusbsilva137) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 3.11.2 +`2021-02-28 ยท 4 ๐Ÿ› ยท 4 ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป` + +### Engine versions +- Node: `12.18.4` +- NPM: `6.14.8` +- MongoDB: `3.4, 3.6, 4.0` +- Apps-Engine: `1.22.2` + +### ๐Ÿ› Bug fixes + + +- External systems not being able to change Omnichannel Inquiry priorities ([#20740](https://github.com/RocketChat/Rocket.Chat/pull/20740)) + + Due to a wrong property name, external applications were not able to change the priority of Omnichannel Inquires. + +- Prevent Message Attachment rendering ([#20860](https://github.com/RocketChat/Rocket.Chat/pull/20860)) + +- Room owner not being able to override global retention policy ([#20727](https://github.com/RocketChat/Rocket.Chat/pull/20727)) + + use correct permissions to check if room owner can override global retention policy + +- Security Hotfix (https://docs.rocket.chat/guides/security/security-updates) + +### ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป Core Team ๐Ÿค“ + +- [@g-thome](https://github.com/g-thome) +- [@ggazzo](https://github.com/ggazzo) +- [@renatobecker](https://github.com/renatobecker) +- [@sampaiodiego](https://github.com/sampaiodiego) + # 3.11.1 `2021-02-10 ยท 5 ๐Ÿ› ยท 6 ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป` diff --git a/app/api/server/helpers/parseJsonQuery.js b/app/api/server/helpers/parseJsonQuery.js index d7eafe0f6da8c..4c2f52e2f8c67 100644 --- a/app/api/server/helpers/parseJsonQuery.js +++ b/app/api/server/helpers/parseJsonQuery.js @@ -2,8 +2,14 @@ import { Meteor } from 'meteor/meteor'; import { EJSON } from 'meteor/ejson'; import { hasPermission } from '../../../authorization'; +import { clean } from '../lib/cleanQuery'; import { API } from '../api'; +const pathAllowConf = { + '/api/v1/users.list': ['$or', '$regex', '$and'], + def: ['$or', '$and', '$regex'], +}; + API.helperMethods.set('parseJsonQuery', function _parseJsonQuery() { let sort; if (this.queryParams.sort) { @@ -54,6 +60,7 @@ API.helperMethods.set('parseJsonQuery', function _parseJsonQuery() { if (this.queryParams.query) { try { query = EJSON.parse(this.queryParams.query); + query = clean(query, pathAllowConf[this.request.route] || pathAllowConf.def); } catch (e) { this.logger.warn(`Invalid query parameter provided "${ this.queryParams.query }":`, e); throw new Meteor.Error('error-invalid-query', `Invalid query parameter provided: "${ this.queryParams.query }"`, { helperMethod: 'parseJsonQuery' }); diff --git a/app/api/server/lib/cleanQuery.ts b/app/api/server/lib/cleanQuery.ts new file mode 100644 index 0000000000000..86f5b288e8155 --- /dev/null +++ b/app/api/server/lib/cleanQuery.ts @@ -0,0 +1,29 @@ +type Query = { [k: string]: any }; + +const denyList = ['constructor', '__proto__', 'prototype']; + +const removeDangerousProps = (v: Query): Query => { + const query = Object.create(null); + for (const key in v) { + if (v.hasOwnProperty(key) && !denyList.includes(key)) { + query[key] = v[key]; + } + } + + return query; +}; + +export function clean(v: Query, allowList: string[] = []): Query { + const typedParam = removeDangerousProps(v); + if (v instanceof Object) { + /* eslint-disable guard-for-in */ + for (const key in typedParam) { + if (/^$/.test(key) && !allowList.includes(key)) { + delete typedParam[key]; + } else { + clean(typedParam[key], allowList); + } + } + } + return typedParam; +} diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index 2e17b8ff9df59..8d4a8da23ed63 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -714,7 +714,8 @@ API.v1.addRoute('users.2fa.sendEmailCode', { const userId = this.userId || Users[method](emailOrUsername, { fields: { _id: 1 } })?._id; if (!userId) { - throw new Meteor.Error('error-invalid-user', 'Invalid user'); + this.logger.error('[2fa] User was not found when requesting 2fa email code'); + return API.v1.success(); } emailCheck.sendEmailCode(getUserForCheck(userId)); diff --git a/app/authorization/server/functions/hasRole.js b/app/authorization/server/functions/hasRole.js index e5d8927cbb747..545adc3f737b6 100644 --- a/app/authorization/server/functions/hasRole.js +++ b/app/authorization/server/functions/hasRole.js @@ -1,7 +1,10 @@ import { Roles } from '../../../models/server/raw'; export const hasRoleAsync = async (userId, roleNames, scope) => { - roleNames = [].concat(roleNames); + if (!userId || userId === '') { + return false; + } + return Roles.isUserInRoles(userId, roleNames, scope); }; diff --git a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js index 72a0a6a0694fa..e635265e1f027 100644 --- a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js +++ b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js @@ -45,6 +45,8 @@ Meteor.methods({ emojiData.aliases = []; } + emojiData.extension = emojiData.extension === 'svg+xml' ? 'png' : emojiData.extension; + let matchingResults = []; if (emojiData._id) { diff --git a/app/emoji-custom/server/methods/uploadEmojiCustom.js b/app/emoji-custom/server/methods/uploadEmojiCustom.js index 1f3d4cf4d1c43..685d4f0270ae0 100644 --- a/app/emoji-custom/server/methods/uploadEmojiCustom.js +++ b/app/emoji-custom/server/methods/uploadEmojiCustom.js @@ -1,13 +1,24 @@ import { Meteor } from 'meteor/meteor'; import limax from 'limax'; +import sharp from 'sharp'; import { hasPermission } from '../../../authorization'; import { RocketChatFile } from '../../../file'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; import { api } from '../../../../server/sdk/api'; +const getFile = async (file, extension) => { + if (extension !== 'svg+xml') { + return file; + } + + return sharp(file) + .png() + .toBuffer(); +}; + Meteor.methods({ - uploadEmojiCustom(binaryContent, contentType, emojiData) { + async uploadEmojiCustom(binaryContent, contentType, emojiData) { if (!hasPermission(this.userId, 'manage-emoji')) { throw new Meteor.Error('not_authorized'); } @@ -15,7 +26,10 @@ Meteor.methods({ emojiData.name = limax(emojiData.name, { replacement: '_' }); // delete aliases for notification purposes. here, it is a string rather than an array delete emojiData.aliases; - const file = new Buffer(binaryContent, 'binary'); + + const file = await getFile(Buffer.from(binaryContent, 'binary'), emojiData.extension); + + emojiData.extension = emojiData.extension === 'svg+xml' ? 'png' : emojiData.extension; const rs = RocketChatFile.bufferToStream(file); RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`)); diff --git a/app/lib/server/functions/setUserAvatar.js b/app/lib/server/functions/setUserAvatar.js index 7229d5a294e4e..bae4c45809af9 100644 --- a/app/lib/server/functions/setUserAvatar.js +++ b/app/lib/server/functions/setUserAvatar.js @@ -18,13 +18,13 @@ export const setUserAvatar = function(user, dataURI, contentType, service) { try { result = HTTP.get(dataURI, { npmRequestOptions: { encoding: 'binary', rejectUnauthorized: false } }); if (!result) { - console.log(`Not a valid response, from the avatar url: ${ dataURI }`); - throw new Meteor.Error('error-avatar-invalid-url', `Invalid avatar URL: ${ dataURI }`, { function: 'setUserAvatar', url: dataURI }); + console.log(`Not a valid response, from the avatar url: ${ encodeURI(dataURI) }`); + throw new Meteor.Error('error-avatar-invalid-url', `Invalid avatar URL: ${ encodeURI(dataURI) }`, { function: 'setUserAvatar', url: dataURI }); } } catch (error) { if (!error.response || error.response.statusCode !== 404) { - console.log(`Error while handling the setting of the avatar from a url (${ dataURI }) for ${ user.username }:`, error); - throw new Meteor.Error('error-avatar-url-handling', `Error while handling avatar setting from a URL (${ dataURI }) for ${ user.username }`, { function: 'RocketChat.setUserAvatar', url: dataURI, username: user.username }); + console.log(`Error while handling the setting of the avatar from a url (${ encodeURI(dataURI) }) for ${ user.username }:`, error); + throw new Meteor.Error('error-avatar-url-handling', `Error while handling avatar setting from a URL (${ encodeURI(dataURI) }) for ${ user.username }`, { function: 'RocketChat.setUserAvatar', url: dataURI, username: user.username }); } } diff --git a/app/livechat/server/methods/loadHistory.js b/app/livechat/server/methods/loadHistory.js index 0ac3331e217a9..910413a2f6316 100644 --- a/app/livechat/server/methods/loadHistory.js +++ b/app/livechat/server/methods/loadHistory.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { loadMessageHistory } from '../../../lib'; -import { LivechatVisitors } from '../../../models'; +import { LivechatVisitors, LivechatRooms } from '../../../models'; Meteor.methods({ 'livechat:loadHistory'({ token, rid, end, limit = 20, ls }) { @@ -12,7 +12,12 @@ Meteor.methods({ const visitor = LivechatVisitors.getVisitorByToken(token, { fields: { _id: 1 } }); if (!visitor) { - return; + throw new Meteor.Error('invalid-visitor', 'Invalid Visitor', { method: 'livechat:loadHistory' }); + } + + const room = LivechatRooms.findOneByIdAndVisitorToken(rid, token, { _id: 1 }); + if (!room) { + throw new Meteor.Error('invalid-room', 'Invalid Room', { method: 'livechat:loadHistory' }); } return loadMessageHistory({ userId: visitor._id, rid, end, limit, ls }); diff --git a/app/mailer/server/api.js b/app/mailer/server/api.js index 395af628b7d19..26839474bc01f 100644 --- a/app/mailer/server/api.js +++ b/app/mailer/server/api.js @@ -8,6 +8,7 @@ import stripHtml from 'string-strip-html'; import { settings } from '../../settings/server'; import { escapeHTML } from '../../../lib/escapeHTML'; +import { replaceVariables } from './utils.js'; let contentHeader; let contentFooter; @@ -25,7 +26,7 @@ settings.get('Language', (key, value) => { }); export const replacekey = (str, key, value = '') => str.replace(new RegExp(`(\\[${ key }\\]|__${ key }__)`, 'igm'), escapeHTML(value)); -export const translate = (str) => str.replace(/\{ ?([^\} ]+)(( ([^\}]+))+)? ?\}/gmi, (match, key) => TAPi18n.__(key, { lng })); +export const translate = (str) => replaceVariables(str, (match, key) => TAPi18n.__(key, { lng })); export const replace = function replace(str, data = {}) { if (!str) { return ''; diff --git a/app/mailer/server/utils.js b/app/mailer/server/utils.js new file mode 100644 index 0000000000000..796215bd89c73 --- /dev/null +++ b/app/mailer/server/utils.js @@ -0,0 +1 @@ +export const replaceVariables = (str, callback) => str.replace(/\{ *([^\{\} ]+)[^\{\}]*\}/gmi, callback); diff --git a/app/mailer/tests/api.tests.js b/app/mailer/tests/api.tests.js new file mode 100644 index 0000000000000..6ecf3b9f9de17 --- /dev/null +++ b/app/mailer/tests/api.tests.js @@ -0,0 +1,66 @@ +/* eslint-env mocha */ +import assert from 'assert'; + +import { replaceVariables } from '../server/utils.js'; + +describe('Mailer-API', function() { + describe('translate', () => { + const i18n = { + key: 'value', + }; + + describe('single key', function functionName() { + it(`should be equal to test ${ i18n.key }`, () => { + assert.equal(`test ${ i18n.key }`, replaceVariables('test {key}', (match, key) => i18n[key])); + }); + }); + + describe('multiple keys', function functionName() { + it(`should be equal to test ${ i18n.key } and ${ i18n.key }`, () => { + assert.equal(`test ${ i18n.key } and ${ i18n.key }`, replaceVariables('test {key} and {key}', (match, key) => i18n[key])); + }); + }); + + describe('key with a trailing space', function functionName() { + it(`should be equal to test ${ i18n.key }`, () => { + assert.equal(`test ${ i18n.key }`, replaceVariables('test {key }', (match, key) => i18n[key])); + }); + }); + + describe('key with a leading space', function functionName() { + it(`should be equal to test ${ i18n.key }`, () => { + assert.equal(`test ${ i18n.key }`, replaceVariables('test { key}', (match, key) => i18n[key])); + }); + }); + + describe('key with leading and trailing spaces', function functionName() { + it(`should be equal to test ${ i18n.key }`, () => { + assert.equal(`test ${ i18n.key }`, replaceVariables('test { key }', (match, key) => i18n[key])); + }); + }); + + describe('key with multiple words', function functionName() { + it(`should be equal to test ${ i18n.key }`, () => { + assert.equal(`test ${ i18n.key }`, replaceVariables('test {key ignore}', (match, key) => i18n[key])); + }); + }); + + describe('key with multiple opening brackets', function functionName() { + it(`should be equal to test {${ i18n.key }`, () => { + assert.equal(`test {${ i18n.key }`, replaceVariables('test {{key}', (match, key) => i18n[key])); + }); + }); + + describe('key with multiple closing brackets', function functionName() { + it(`should be equal to test ${ i18n.key }}`, () => { + assert.equal(`test ${ i18n.key }}`, replaceVariables('test {key}}', (match, key) => i18n[key])); + }); + }); + + describe('key with multiple opening and closing brackets', function functionName() { + it(`should be equal to test {${ i18n.key }}`, () => { + assert.equal(`test {${ i18n.key }}`, replaceVariables('test {{key}}', (match, key) => i18n[key])); + }); + }); + }); +}); diff --git a/app/meteor-accounts-saml/server/lib/SAML.ts b/app/meteor-accounts-saml/server/lib/SAML.ts index 277084414a552..2514cce35ccb6 100644 --- a/app/meteor-accounts-saml/server/lib/SAML.ts +++ b/app/meteor-accounts-saml/server/lib/SAML.ts @@ -376,7 +376,7 @@ export class SAML { }); } - private static processValidateAction(req: IIncomingMessage, res: ServerResponse, service: IServiceProviderOptions, samlObject: ISAMLAction): void { + private static processValidateAction(req: IIncomingMessage, res: ServerResponse, service: IServiceProviderOptions, _samlObject: ISAMLAction): void { const serviceProvider = new SAMLServiceProvider(service); SAMLUtils.relayState = req.body.RelayState; serviceProvider.validateResponse(req.body.SAMLResponse, (err, profile/* , loggedOut*/) => { @@ -390,21 +390,15 @@ export class SAML { throw new Error('No user data collected from IdP response.'); } - let credentialToken = (profile.inResponseToId && profile.inResponseToId.value) || profile.inResponseToId || profile.InResponseTo || samlObject.credentialToken; + // create a random token to store the login result + // to test an IdP initiated login on localhost, use the following URL (assuming SimpleSAMLPHP on localhost:8080): + // http://localhost:8080/simplesaml/saml2/idp/SSOService.php?spentityid=http://localhost:3000/_saml/metadata/test-sp + const credentialToken = Random.id(); + const loginResult = { profile, }; - if (!credentialToken) { - // If the login was initiated by the IDP, then we don't have a credentialToken as there was no AuthorizeRequest on our side - // so we create a random token now to use the same url to end the login - // - // to test an IdP initiated login on localhost, use the following URL (assuming SimpleSAMLPHP on localhost:8080): - // http://localhost:8080/simplesaml/saml2/idp/SSOService.php?spentityid=http://localhost:3000/_saml/metadata/test-sp - credentialToken = Random.id(); - SAMLUtils.log('[SAML] Using random credentialToken: ', credentialToken); - } - this.storeCredential(credentialToken, loginResult); const url = `${ Meteor.absoluteUrl('home') }?saml_idp_credentialToken=${ credentialToken }`; res.writeHead(302, { diff --git a/app/meteor-accounts-saml/server/loginHandler.ts b/app/meteor-accounts-saml/server/loginHandler.ts index 0dcec76d04c3a..84beec3091aae 100644 --- a/app/meteor-accounts-saml/server/loginHandler.ts +++ b/app/meteor-accounts-saml/server/loginHandler.ts @@ -11,7 +11,7 @@ const makeError = (message: string): Record => ({ }); Accounts.registerLoginHandler('saml', function(loginRequest) { - if (!loginRequest.saml || !loginRequest.credentialToken) { + if (!loginRequest.saml || !loginRequest.credentialToken || typeof loginRequest.credentialToken !== 'string') { return undefined; } diff --git a/app/models/server/raw/Roles.js b/app/models/server/raw/Roles.js index 523aa968057d1..de771bcad9b01 100644 --- a/app/models/server/raw/Roles.js +++ b/app/models/server/raw/Roles.js @@ -8,13 +8,15 @@ export class RolesRaw extends BaseRaw { } async isUserInRoles(userId, roles, scope) { - roles = [].concat(roles); + if (!Array.isArray(roles)) { + roles = [roles]; + } for (let i = 0, total = roles.length; i < total; i++) { const roleName = roles[i]; // eslint-disable-next-line no-await-in-loop - const role = await this.findOne({ _id: roleName }); + const role = await this.findOne({ _id: roleName }, { scope: 1 }); const roleScope = (role && role.scope) || 'Users'; const model = this.models[roleScope]; diff --git a/app/ui-utils/client/lib/Layout.js b/app/ui-utils/client/lib/Layout.js index 758f58a99fa78..2158d80f2a9be 100644 --- a/app/ui-utils/client/lib/Layout.js +++ b/app/ui-utils/client/lib/Layout.js @@ -1,14 +1,17 @@ import { Tracker } from 'meteor/tracker'; import { FlowRouter } from 'meteor/kadira:flow-router'; +import { ReactiveVar } from 'meteor/reactive-var'; export const Layout = new class RocketChatLayout { constructor() { + this.embedded = new ReactiveVar(); Tracker.autorun(() => { this.layout = FlowRouter.getQueryParam('layout'); + this.embedded.set(this.layout === 'embedded'); }); } isEmbedded() { - return FlowRouter.getQueryParam('layout') === 'embedded'; + return this.embedded.get(); } }(); diff --git a/app/ui-utils/client/lib/messageContext.js b/app/ui-utils/client/lib/messageContext.js index d10e7cd8ccbf8..283cddf5bb9e0 100644 --- a/app/ui-utils/client/lib/messageContext.js +++ b/app/ui-utils/client/lib/messageContext.js @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Template } from 'meteor/templating'; import { FlowRouter } from 'meteor/kadira:flow-router'; +import { Tracker } from 'meteor/tracker'; import { Subscriptions, Rooms, Users } from '../../../models/client'; import { hasPermission } from '../../../authorization/client'; @@ -17,7 +18,7 @@ const fields = { name: 1, username: 1, 'settings.preferences.showMessageInMainTh export function messageContext({ rid } = Template.instance()) { const uid = Meteor.userId(); const user = Users.findOne({ _id: uid }, { fields }) || {}; - const instace = Template.instance(); + const instance = Template.instance(); const openThread = (e) => { const { rid, mid, tmid } = e.currentTarget.dataset; const room = Rooms.findOne({ _id: rid }); @@ -40,7 +41,7 @@ export function messageContext({ rid } = Template.instance()) { }); } : (msg, e) => { const { actionlink } = e.currentTarget.dataset; - actionLinks.run(actionlink, msg._id, instace, (err) => { + actionLinks.run(actionlink, msg._id, instance, (err) => { if (err) { handleError(err); } @@ -60,13 +61,12 @@ export function messageContext({ rid } = Template.instance()) { return { u: user, - room: Rooms.findOne({ _id: rid }, { - reactive: false, + room: Tracker.nonreactive(() => Rooms.findOne({ _id: rid }, { fields: { _updatedAt: 0, lastMessage: 0, }, - }), + })), subscription: Subscriptions.findOne({ rid }, { fields: { name: 1, diff --git a/app/utils/rocketchat.info b/app/utils/rocketchat.info index 510b977ecfb6a..85e29a0aefeda 100644 --- a/app/utils/rocketchat.info +++ b/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "3.11.1" + "version": "3.11.6" } diff --git a/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.js b/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.js index 318ec2623c84f..0f346929ec472 100644 --- a/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.js +++ b/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.js @@ -226,7 +226,7 @@ function EditChannel({ room, onClickClose, onClickBack }) { const canChangeType = getCanChangeType(room, canCreateChannel, canCreateGroup, isAdmin); const canSetRo = usePermission('set-readonly', room._id); const canSetReactWhenRo = usePermission('set-react-when-readonly', room._id); - const canEditPrivilegedSetting = usePermission('edit-privileged-setting', room._id); + const canEditRoomRetentionPolicy = usePermission('edit-room-retention-policy', room._id); const canArchiveOrUnarchive = useAtLeastOnePermission(useMemo(() => ['archive-room', 'unarchive-room'], [])); const canDelete = usePermission(`delete-${ room.t }`); const canToggleEncryption = usePermission('toggle-room-e2e-encryption', room._id) && (room.encrypted || e2e.isReady()); @@ -390,7 +390,7 @@ function EditChannel({ room, onClickClose, onClickBack }) { {t('RetentionPolicyRoom_OverrideGlobal')} - + diff --git a/ee/app/livechat-enterprise/server/api/lib/inquiries.js b/ee/app/livechat-enterprise/server/api/lib/inquiries.js index 8a7423c98b477..ac1527155e8ed 100644 --- a/ee/app/livechat-enterprise/server/api/lib/inquiries.js +++ b/ee/app/livechat-enterprise/server/api/lib/inquiries.js @@ -7,7 +7,7 @@ export async function setPriorityToInquiry({ userId, roomId, priority }) { if (!await hasPermissionAsync(userId, 'manage-livechat-priorities') && !await hasPermissionAsync(userId, 'view-l-room')) { throw new Error('error-not-authorized'); } - const inquiry = await LivechatInquiry.findOneByRoomId(roomId, { fields: { queued: 1 } }); + const inquiry = await LivechatInquiry.findOneByRoomId(roomId, { fields: { status: 1 } }); if (!inquiry || inquiry.status !== 'queued') { throw new Error('error-invalid-inquiry'); } diff --git a/ee/client/omnichannel/additionalForms/CustomFieldsAdditionalForm.js b/ee/client/omnichannel/additionalForms/CustomFieldsAdditionalForm.js index d728bb08933b7..425037cd9f020 100644 --- a/ee/client/omnichannel/additionalForms/CustomFieldsAdditionalForm.js +++ b/ee/client/omnichannel/additionalForms/CustomFieldsAdditionalForm.js @@ -12,7 +12,13 @@ const getInitialValues = (data) => ({ public: !!data.public, }); -const checkInvalidOptions = (value) => value.trim() !== '' && !/^([a-zA-Z0-9-_ ]+)(,\s*[a-zA-Z0-9-_ ]+)*$/i.test(value); +const checkInvalidOptions = (value) => { + if (!value || value.trim() === '') { + return false; + } + + return value.split(',').every((v) => /^[a-zA-Z0-9-_ ]+$/.test(v)); +}; const CustomFieldsAdditionalFormContainer = ({ data = {}, state, onChange, className }) => { const t = useTranslation(); diff --git a/package-lock.json b/package-lock.json index 6d8b13fb1e45e..1aaa34235b839 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "Rocket.Chat", - "version": "3.11.1", + "version": "3.11.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -6014,9 +6014,9 @@ "integrity": "sha512-Q6llwucmJX5f9+GfjrUW+bR/XVtxxpF0RrUaxaL1n+IRjUPl19GdQ5ycyzSuzI96UMzLqeV4fZxIUmxnN/GY+Q==" }, "@rocket.chat/livechat": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.7.6.tgz", - "integrity": "sha512-EcnACq6jXRc3+B0WEY1/jKHboEv03Zh9iRi4a52HD4y3TRkrNxrGivfQ5h2TravTm/HM8srjqjocC4w9f2u1Kg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.9.0.tgz", + "integrity": "sha512-4VbwxCfcX18Ulk63ySHiND1DfEXIRPOPJbQAWfQuFvtggB3QkpkLuPaDevW++wFBwMl4Gy3BAOgTwq3d9AVCyw==", "dev": true, "requires": { "@kossnocorp/desvg": "^0.2.0", @@ -6025,6 +6025,7 @@ "css-vars-ponyfill": "^2.3.2", "date-fns": "^2.15.0", "desvg": "^1.0.2", + "dompurify": "^2.2.6", "emoji-mart": "^3.0.0", "history": "^5.0.0", "i18nline": "^2.0.1", @@ -6045,20 +6046,21 @@ "dev": true }, "query-string": { - "version": "6.13.7", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.7.tgz", - "integrity": "sha512-CsGs8ZYb39zu0WLkeOhe0NMePqgYdAuCqxOYKDR5LVCytDZYMGx3Bb+xypvQvPHVPijRXB0HZNFllCzHRe4gEA==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", + "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", "dev": true, "requires": { "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "whatwg-fetch": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz", - "integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", "dev": true } } @@ -16883,9 +16885,9 @@ } }, "css-vars-ponyfill": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/css-vars-ponyfill/-/css-vars-ponyfill-2.4.1.tgz", - "integrity": "sha512-duoN5j10VJPdPUgo/H4PwvxhSxhJWgfymjeIGpTA7+KzQ2AmeFHnlKwrCYC9Ii+HUpC8BrIS5fhwcuaNOs/Hrg==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/css-vars-ponyfill/-/css-vars-ponyfill-2.4.3.tgz", + "integrity": "sha512-PBfIwjSu27s8kebu8taEYFM8ehVr8o2Qw4H4nSlJzHAJgcduAqxz4oPmYTJuzgauOKaWII9SHWStQ965fxsdZA==", "dev": true }, "css-what": { @@ -17257,9 +17259,9 @@ "integrity": "sha512-lcWy3AXDRJOD7MplwZMmNSRM//kZtJaLz4n6D1P5z9wEmZGBKhJRBIr1Xs9KNQJmdXPblvgffynYji4iylUTcA==" }, "date-fns": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", - "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.19.0.tgz", + "integrity": "sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==", "dev": true }, "date-now": { @@ -17888,9 +17890,9 @@ "integrity": "sha1-BpYswKRCFnWbo7mOOyV3wM4w/Aw=" }, "emoji-mart": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-3.0.0.tgz", - "integrity": "sha512-r5DXyzOLJttdwRYfJmPq/XL3W5tiAE/VsRnS0Hqyn27SqPA/GOYwVUSx50px/dXdJyDSnvmoPbuJ/zzhwSaU4A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-3.0.1.tgz", + "integrity": "sha512-sxpmMKxqLvcscu6mFn9ITHeZNkGzIvD0BSNFE/LJESPbCA8s1jM6bCDPjWbV31xHq7JXaxgpHxLB54RCbBZSlg==", "dev": true, "requires": { "@babel/runtime": "^7.0.0", @@ -19445,6 +19447,12 @@ } } }, + "filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", + "dev": true + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -19890,7 +19898,7 @@ }, "chownr": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true @@ -19925,7 +19933,7 @@ }, "debug": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, @@ -19956,7 +19964,7 @@ }, "fs-minipass": { "version": "1.2.5", - "resolved": "", + "resolved": false, "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, @@ -19990,7 +19998,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, @@ -20022,7 +20030,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -20043,7 +20051,7 @@ }, "inherits": { "version": "2.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true @@ -20091,7 +20099,7 @@ }, "minipass": { "version": "2.3.5", - "resolved": "", + "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, @@ -20102,7 +20110,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, @@ -20122,7 +20130,7 @@ }, "ms": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true @@ -20136,7 +20144,7 @@ }, "needle": { "version": "2.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, @@ -20148,7 +20156,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": "", + "resolved": false, "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -20178,14 +20186,14 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": "", + "resolved": false, "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, @@ -20265,7 +20273,7 @@ }, "process-nextick-args": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true @@ -20310,7 +20318,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": "", + "resolved": false, "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, @@ -20341,7 +20349,7 @@ }, "semver": { "version": "5.7.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true @@ -20401,7 +20409,7 @@ }, "tar": { "version": "4.4.8", - "resolved": "", + "resolved": false, "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, @@ -20441,7 +20449,7 @@ }, "yallist": { "version": "3.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true @@ -29696,9 +29704,9 @@ } }, "preact": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.5.tgz", - "integrity": "sha512-5ONLNH1SXMzzbQoExZX4TELemNt+TEDb622xXFNfZngjjM9qtrzseJt+EfiUu4TZ6EJ95X5sE1ES4yqHFSIdhg==", + "version": "10.5.13", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.13.tgz", + "integrity": "sha512-q/vlKIGNwzTLu+jCcvywgGrt+H/1P/oIRSD6mV4ln3hmlC+Aa34C7yfPI4+5bzW8pONyVXYS7SvXosy2dKKtWQ==", "dev": true }, "preact-i18nline": { @@ -31392,12 +31400,6 @@ "source-map": "~0.6.1" }, "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 19cbf5138b617..2f4b141d1d3b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Rocket.Chat", "description": "The Ultimate Open Source WebChat Platform", - "version": "3.11.1", + "version": "3.11.6", "author": { "name": "Rocket.Chat", "url": "https://rocket.chat/" @@ -56,7 +56,7 @@ "@babel/register": "^7.12.1", "@octokit/rest": "^16.43.2", "@rocket.chat/eslint-config": "^0.3.0", - "@rocket.chat/livechat": "^1.7.6", + "@rocket.chat/livechat": "^1.9.0", "@settlin/spacebars-loader": "^1.0.8", "@storybook/addon-essentials": "^6.1.11", "@storybook/addons": "^6.1.11", diff --git a/server/lib/isPresenceMonitorEnabled.ts b/server/lib/isPresenceMonitorEnabled.ts new file mode 100644 index 0000000000000..2616337decce2 --- /dev/null +++ b/server/lib/isPresenceMonitorEnabled.ts @@ -0,0 +1,6 @@ +const startMonitor = typeof process.env.DISABLE_PRESENCE_MONITOR === 'undefined' + || !['true', 'yes'].includes(String(process.env.DISABLE_PRESENCE_MONITOR).toLowerCase()); + +export function isPresenceMonitorEnabled(): boolean { + return startMonitor; +} diff --git a/server/methods/addAllUserToRoom.js b/server/methods/addAllUserToRoom.js index d8379b2030c71..83dffa5abc9e7 100644 --- a/server/methods/addAllUserToRoom.js +++ b/server/methods/addAllUserToRoom.js @@ -11,52 +11,54 @@ Meteor.methods({ check(rid, String); check(activeUsersOnly, Boolean); - if (hasRole(this.userId, 'admin') === true) { - const userCount = Users.find().count(); - if (userCount > settings.get('API_User_Limit')) { - throw new Meteor.Error('error-user-limit-exceeded', 'User Limit Exceeded', { - method: 'addAllToRoom', - }); - } + if (!hasRole(this.userId, 'admin')) { + throw new Meteor.Error(403, 'Access to Method Forbidden', { + method: 'addAllToRoom', + }); + } - const room = Rooms.findOneById(rid); - if (room == null) { - throw new Meteor.Error('error-invalid-room', 'Invalid room', { - method: 'addAllToRoom', - }); - } + const userFilter = {}; + if (activeUsersOnly === true) { + userFilter.active = true; + } - const userFilter = {}; - if (activeUsersOnly === true) { - userFilter.active = true; - } + const userCursor = Users.find(userFilter); + const usersCount = userCursor.count(); + if (usersCount > settings.get('API_User_Limit')) { + throw new Meteor.Error('error-user-limit-exceeded', 'User Limit Exceeded', { + method: 'addAllToRoom', + }); + } - const users = Users.find(userFilter).fetch(); - const now = new Date(); - users.forEach(function(user) { - const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, user._id); - if (subscription != null) { - return; - } - callbacks.run('beforeJoinRoom', user, room); - Subscriptions.createWithRoomAndUser(room, user, { - ts: now, - open: true, - alert: true, - unread: 1, - userMentions: 1, - groupMentions: 0, - }); - Messages.createUserJoinWithRoomIdAndUser(rid, user, { - ts: now, - }); - Meteor.defer(function() {}); - return callbacks.run('afterJoinRoom', user, room); + const room = Rooms.findOneById(rid); + if (!room) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { + method: 'addAllToRoom', }); - return true; } - throw new Meteor.Error(403, 'Access to Method Forbidden', { - method: 'addAllToRoom', + + const users = userCursor.fetch(); + const now = new Date(); + users.forEach(function(user) { + const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, user._id); + if (subscription != null) { + return; + } + callbacks.run('beforeJoinRoom', user, room); + Subscriptions.createWithRoomAndUser(room, user, { + ts: now, + open: true, + alert: true, + unread: 1, + userMentions: 1, + groupMentions: 0, + }); + Messages.createUserJoinWithRoomIdAndUser(rid, user, { + ts: now, + }); + Meteor.defer(function() {}); + return callbacks.run('afterJoinRoom', user, room); }); + return true; }, }); diff --git a/server/methods/getPasswordPolicy.js b/server/methods/getPasswordPolicy.js index c4d6a284b4a10..47fadb436ee57 100644 --- a/server/methods/getPasswordPolicy.js +++ b/server/methods/getPasswordPolicy.js @@ -1,10 +1,13 @@ import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; import { Users } from '../../app/models'; import { passwordPolicy } from '../../app/lib'; Meteor.methods({ - getPasswordPolicy(params) { + getPasswordPolicy(params = {}) { + check(params, { token: String }); + const user = Users.findOne({ 'services.password.reset.token': params.token }); if (!user && !Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index ca202fc85ee35..c8e2f60852091 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -32,6 +32,7 @@ import { IntegrationsRaw } from '../../../app/models/server/raw/Integrations'; import { EventSignatures } from '../../sdk/lib/Events'; import { IEmailInbox } from '../../../definition/IEmailInbox'; import { EmailInboxRaw } from '../../../app/models/server/raw/EmailInbox'; +import { isPresenceMonitorEnabled } from '../../lib/isPresenceMonitorEnabled'; interface IModelsParam { Subscriptions: SubscriptionsRaw; @@ -157,22 +158,24 @@ export function initWatchers(models: IModelsParam, broadcast: BroadcastCallback, }); }); - watch(UsersSessions, async ({ clientAction, id, data }) => { - switch (clientAction) { - case 'inserted': - case 'updated': - data = data ?? await UsersSessions.findOneById(id); - if (!data) { - return; - } + if (isPresenceMonitorEnabled()) { + watch(UsersSessions, async ({ clientAction, id, data: eventData }) => { + switch (clientAction) { + case 'inserted': + case 'updated': + const data = eventData ?? await UsersSessions.findOneById(id); + if (!data) { + return; + } - broadcast('watch.userSessions', { clientAction, userSession: data }); - break; - case 'removed': - broadcast('watch.userSessions', { clientAction, userSession: { _id: id } }); - break; - } - }); + broadcast('watch.userSessions', { clientAction, userSession: data }); + break; + case 'removed': + broadcast('watch.userSessions', { clientAction, userSession: { _id: id } }); + break; + } + }); + } watch(LivechatInquiry, async ({ clientAction, id, data, diff }) => { switch (clientAction) { diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index 495c19994dc60..5e0e3d8d17338 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -20,7 +20,7 @@ import { integrations } from '../../../app/integrations/server/lib/triggerHandle import { ListenersModule, minimongoChangeMap } from '../../modules/listeners/listeners.module'; import notifications from '../../../app/notifications/server/lib/Notifications'; import { configureEmailInboxes } from '../../features/EmailInbox/EmailInbox'; - +import { isPresenceMonitorEnabled } from '../../lib/isPresenceMonitorEnabled'; const autoUpdateRecords = new Map(); @@ -134,18 +134,20 @@ export class MeteorService extends ServiceClass implements IMeteor { }); // TODO: May need to merge with https://github.com/RocketChat/Rocket.Chat/blob/0ddc2831baf8340cbbbc432f88fc2cb97be70e9b/ee/server/services/Presence/Presence.ts#L28 - this.onEvent('watch.userSessions', async ({ clientAction, userSession }): Promise => { - if (clientAction === 'removed') { - UserPresenceMonitor.processUserSession({ - _id: userSession._id, - connections: [{ - fake: true, - }], - }, 'removed'); - } + if (isPresenceMonitorEnabled()) { + this.onEvent('watch.userSessions', async ({ clientAction, userSession }): Promise => { + if (clientAction === 'removed') { + UserPresenceMonitor.processUserSession({ + _id: userSession._id, + connections: [{ + fake: true, + }], + }, 'removed'); + } - UserPresenceMonitor.processUserSession(userSession, minimongoChangeMap[clientAction]); - }); + UserPresenceMonitor.processUserSession(userSession, minimongoChangeMap[clientAction]); + }); + } this.onEvent('watch.instanceStatus', async ({ clientAction, id, data }): Promise => { if (clientAction === 'removed') { diff --git a/server/startup/presence.js b/server/startup/presence.js index bcad81c80bb36..73fc47b2725c0 100644 --- a/server/startup/presence.js +++ b/server/startup/presence.js @@ -3,27 +3,27 @@ import { UserPresence } from 'meteor/konecty:user-presence'; import InstanceStatusModel from '../../app/models/server/models/InstanceStatus'; import UsersSessionsModel from '../../app/models/server/models/UsersSessions'; +import { isPresenceMonitorEnabled } from '../lib/isPresenceMonitorEnabled'; Meteor.startup(function() { UserPresence.start(); - const startMonitor = typeof process.env.DISABLE_PRESENCE_MONITOR === 'undefined' - || !['true', 'yes'].includes(String(process.env.DISABLE_PRESENCE_MONITOR).toLowerCase()); - if (startMonitor) { - // UserPresenceMonitor.start(); + if (!isPresenceMonitorEnabled()) { + return; + } + // UserPresenceMonitor.start(); - // Remove lost connections - const ids = InstanceStatusModel.find({}, { fields: { _id: 1 } }).fetch().map((id) => id._id); + // Remove lost connections + const ids = InstanceStatusModel.find({}, { fields: { _id: 1 } }).fetch().map((id) => id._id); - const update = { - $pull: { - connections: { - instanceId: { - $nin: ids, - }, + const update = { + $pull: { + connections: { + instanceId: { + $nin: ids, }, }, - }; - UsersSessionsModel.update({}, update, { multi: true }); - } + }, + }; + UsersSessionsModel.update({}, update, { multi: true }); }); diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index 3496519828217..9b9bfb4e088d6 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -12,13 +12,11 @@ import { isDocker, getURL } from '../../app/utils'; import { Users } from '../../app/models/server'; import InstanceStatusModel from '../../app/models/server/models/InstanceStatus'; import { StreamerCentral } from '../modules/streamer/streamer.module'; +import { isPresenceMonitorEnabled } from '../lib/isPresenceMonitorEnabled'; process.env.PORT = String(process.env.PORT).trim(); process.env.INSTANCE_IP = String(process.env.INSTANCE_IP).trim(); -const startMonitor = typeof process.env.DISABLE_PRESENCE_MONITOR === 'undefined' - || !['true', 'yes'].includes(String(process.env.DISABLE_PRESENCE_MONITOR).toLowerCase()); - const connections = {}; this.connections = connections; @@ -61,7 +59,7 @@ const cache = new Map(); const originalSetDefaultStatus = UserPresence.setDefaultStatus; export let matrixBroadCastActions; function startMatrixBroadcast() { - if (!startMonitor) { + if (!isPresenceMonitorEnabled()) { UserPresence.setDefaultStatus = originalSetDefaultStatus; } @@ -155,7 +153,7 @@ function startStreamCastBroadcast(value) { logger.connection.info('connecting in', instance, value); - if (!startMonitor) { + if (!isPresenceMonitorEnabled()) { UserPresence.setDefaultStatus = (id, status) => { Users.updateDefaultStatus(id, status); };