From 87f2bdef528964b04d6585d76714d29842018da9 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 6 Jul 2020 15:00:03 -0300 Subject: [PATCH 01/18] Started implementation --- app/api/server/v1/settings.js | 9 +- app/models/server/models/Settings.js | 4 + app/settings/client/lib/settings.ts | 62 ++++++++-- app/settings/lib/settings.ts | 38 ++++++ app/settings/server/functions/settings.ts | 112 +++++++++--------- ee/app/canned-responses/server/settings.js | 5 + ee/app/ldap-enterprise/server/settings.js | 30 +++++ ee/app/license/server/license.ts | 9 ++ ee/app/livechat-enterprise/server/settings.js | 38 +++++- ee/app/settings/server/index.js | 1 + ee/app/settings/server/settings.ts | 46 +++++++ ee/client/index.js | 1 + ee/server/index.js | 1 + server/publications/settings/index.js | 6 + 14 files changed, 291 insertions(+), 71 deletions(-) create mode 100644 ee/app/settings/server/index.js create mode 100644 ee/app/settings/server/settings.ts diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.js index 284fea491be7..930f76be5c3c 100644 --- a/app/api/server/v1/settings.js +++ b/app/api/server/v1/settings.js @@ -6,6 +6,7 @@ import _ from 'underscore'; import { Settings } from '../../../models/server'; import { hasPermission } from '../../../authorization'; import { API } from '../api'; +import { settings as settingsLib } from '../../../settings/server'; // settings endpoints API.v1.addRoute('settings.public', { authRequired: false }, { @@ -20,12 +21,12 @@ API.v1.addRoute('settings.public', { authRequired: false }, { ourQuery = Object.assign({}, query, ourQuery); - const settings = Settings.find(ourQuery, { + const settings = settingsLib.fetchSettings(ourQuery, { sort: sort || { _id: 1 }, skip: offset, limit: count, fields: Object.assign({ _id: 1, value: 1 }, fields), - }).fetch(); + }); return API.v1.success({ settings, @@ -94,12 +95,12 @@ API.v1.addRoute('settings', { authRequired: true }, { ourQuery = Object.assign({}, query, ourQuery); - const settings = Settings.find(ourQuery, { + const settings = settingsLib.fetchSettings(ourQuery, { sort: sort || { _id: 1 }, skip: offset, limit: count, fields: Object.assign({ _id: 1, value: 1 }, fields), - }).fetch(); + }); return API.v1.success({ settings, diff --git a/app/models/server/models/Settings.js b/app/models/server/models/Settings.js index a03c81a04100..bd4ca7960897 100644 --- a/app/models/server/models/Settings.js +++ b/app/models/server/models/Settings.js @@ -105,6 +105,10 @@ export class Settings extends Base { return this.find({ wizard: { $exists: true, $ne: null } }); } + findEnterpriseSettings() { + return this.find({ enterprise: true }); + } + // UPDATE updateValueById(_id, value) { const query = { diff --git a/app/settings/client/lib/settings.ts b/app/settings/client/lib/settings.ts index 6a9597e9df48..05945a9d95e9 100644 --- a/app/settings/client/lib/settings.ts +++ b/app/settings/client/lib/settings.ts @@ -2,7 +2,10 @@ import { Meteor } from 'meteor/meteor'; import { ReactiveDict } from 'meteor/reactive-dict'; import { PublicSettingsCachedCollection } from '../../../../client/lib/settings/PublicSettingsCachedCollection'; -import { SettingsBase, SettingValue } from '../../lib/settings'; +import { SettingsBase, SettingValue, ISettingRecord } from '../../lib/settings'; +import { hasLicense } from '../../../../ee/app/license/client'; + +let isEnterprise = false; class Settings extends SettingsBase { cachedCollection = PublicSettingsCachedCollection.get() @@ -15,20 +18,43 @@ class Settings extends SettingsBase { return this.dict.get(_id); } + getRecordValue(record: ISettingRecord): SettingValue { + const { value, invalidValue } = record; + + if (!record.enterprise) { + return value; + } + + if (!isEnterprise) { + return invalidValue; + } + + if (!record.modules?.length) { + return value; + } + + for (const moduleName of record.modules) { + if (!hasLicense(moduleName)) { + return invalidValue; + } + } + + return value; + } + + private _storeSettingValue(record: ISettingRecord, initialLoad: boolean): void { + const value = this.getRecordValue(record); + Meteor.settings[record._id] = value; + this.dict.set(record._id, value); + this.load(record._id, value, initialLoad); + } + init(): void { let initialLoad = true; this.collection.find().observe({ - added: (record: {_id: string; value: SettingValue}) => { - Meteor.settings[record._id] = record.value; - this.dict.set(record._id, record.value); - this.load(record._id, record.value, initialLoad); - }, - changed: (record: {_id: string; value: SettingValue}) => { - Meteor.settings[record._id] = record.value; - this.dict.set(record._id, record.value); - this.load(record._id, record.value, initialLoad); - }, - removed: (record: {_id: string}) => { + added: (record: ISettingRecord) => this._storeSettingValue(record, initialLoad), + changed: (record: ISettingRecord) => this._storeSettingValue(record, initialLoad), + removed: (record: ISettingRecord) => { delete Meteor.settings[record._id]; this.dict.set(record._id, null); this.load(record._id, undefined, initialLoad); @@ -41,3 +67,15 @@ class Settings extends SettingsBase { export const settings = new Settings(); settings.init(); + +Meteor.startup(() => { + Meteor.call('license:isEnterprise', (err: any, result: any) => { + if (err) { + throw err; + } + + if (result) { + isEnterprise = true; + } + }); +}); diff --git a/app/settings/lib/settings.ts b/app/settings/lib/settings.ts index 4bc180c99415..fe32a53340c8 100644 --- a/app/settings/lib/settings.ts +++ b/app/settings/lib/settings.ts @@ -6,6 +6,44 @@ export type SettingValueRoomPick = Array<{_id: string; name: string}> | string export type SettingValue = string | boolean | number | SettingValueMultiSelect | undefined; export type SettingComposedValue = {key: string; value: SettingValue}; export type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; +export interface ISettingAddOptions { + _id?: string; + type?: 'group' | 'boolean' | 'int' | 'string' | 'asset' | 'code' | 'select' | 'password' | 'action' | 'relativeUrl' | 'language' | 'date' | 'color' | 'font' | 'roomPick' | 'multiSelect'; + editor?: string; + packageEditor?: string; + packageValue?: SettingValue; + valueSource?: string; + hidden?: boolean; + blocked?: boolean; + secret?: boolean; + sorter?: number; + i18nLabel?: string; + i18nDescription?: string; + autocomplete?: boolean; + force?: boolean; + group?: string; + section?: string; + enableQuery?: any; + processEnvValue?: SettingValue; + meteorSettingsValue?: SettingValue; + value?: SettingValue; + ts?: Date; + multiline?: boolean; + values?: Array; + public?: boolean; + enterprise?: boolean; + modules?: Array; + invalidValue?: any; +} +export interface ISettingSelectOption { + key: string; + i18nLabel: string; +} +export interface ISettingRecord extends ISettingAddOptions { + _id: string; + env: boolean; + value: SettingValue; +} interface ISettingRegexCallbacks { regex: RegExp; diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts index 07eb26187b38..78c764905c1a 100644 --- a/app/settings/server/functions/settings.ts +++ b/app/settings/server/functions/settings.ts @@ -1,7 +1,9 @@ +import { EventEmitter } from 'events'; + import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { SettingsBase, SettingValue } from '../../lib/settings'; +import { SettingsBase, SettingValue, ISettingAddOptions, ISettingRecord } from '../../lib/settings'; import SettingsModel from '../../../models/server/models/Settings'; const blockedSettings = new Set(); @@ -15,6 +17,8 @@ if (process.env.SETTINGS_HIDDEN) { process.env.SETTINGS_HIDDEN.split(',').forEach((settingId) => hiddenSettings.add(settingId.trim())); } +export const SettingsEvents = new EventEmitter(); + const overrideSetting = (_id: string, value: SettingValue, options: ISettingAddOptions): SettingValue => { const envValue = process.env[_id]; if (envValue) { @@ -54,38 +58,6 @@ const overrideSetting = (_id: string, value: SettingValue, options: ISettingAddO return value; }; -export interface ISettingAddOptions { - _id?: string; - type?: 'group' | 'boolean' | 'int' | 'string' | 'asset' | 'code' | 'select' | 'password' | 'action' | 'relativeUrl' | 'language' | 'date' | 'color' | 'font' | 'roomPick' | 'multiSelect'; - editor?: string; - packageEditor?: string; - packageValue?: SettingValue; - valueSource?: string; - hidden?: boolean; - blocked?: boolean; - secret?: boolean; - sorter?: number; - i18nLabel?: string; - i18nDescription?: string; - autocomplete?: boolean; - force?: boolean; - group?: string; - section?: string; - enableQuery?: any; - processEnvValue?: SettingValue; - meteorSettingsValue?: SettingValue; - value?: SettingValue; - ts?: Date; - multiline?: boolean; - values?: Array; - public?: boolean; -} - -export interface ISettingSelectOption { - key: string; - i18nLabel: string; -} - export interface ISettingAddGroupOptions { hidden?: boolean; blocked?: boolean; @@ -94,6 +66,7 @@ export interface ISettingAddGroupOptions { i18nDescription?: string; } + interface IUpdateOperator { $set: ISettingAddOptions; $setOnInsert: ISettingAddOptions & { @@ -143,6 +116,13 @@ class Settings extends SettingsBase { options.hidden = options.hidden || false; options.blocked = options.blocked || false; options.secret = options.secret || false; + options.enterprise = options.enterprise || false; + + if (options.enterprise && !('invalidValue' in options)) { + console.error(`Enterprise setting ${ _id } is missing the invalidValue option`); + throw new Error(`Enterprise setting ${ _id } is missing the invalidValue option`); + } + if (options.group && options.sorter == null) { options.sorter = this._sorter[options.group]++; } @@ -329,33 +309,57 @@ class Settings extends SettingsBase { return SettingsModel.updateValueById(_id, undefined); } + /* + * Change a setting value on the Meteor.settings object + */ + storeSettingValue(record: ISettingRecord, initialLoad: boolean): void { + const newData = { + value: record.enterprise ? record.invalidValue : record.value, + }; + SettingsEvents.emit('store-setting-value', record, newData); + const { value } = newData; + + Meteor.settings[record._id] = value; + if (record.env === true) { + process.env[record._id] = String(value); + } + + this.load(record._id, value, initialLoad); + } + + /* + * Remove a setting value on the Meteor.settings object + */ + removeSettingValue(record: ISettingRecord, initialLoad: boolean): void { + SettingsEvents.emit('remove-setting-value', record); + + delete Meteor.settings[record._id]; + if (record.env === true) { + delete process.env[record._id]; + } + + this.load(record._id, undefined, initialLoad); + } + + /* + * Runs a query on the settings module and pass the fetched data through an event before returning + */ + fetchSettings(query: Record, options: Record): void { + const settings = SettingsModel.find(query, options).fetch(); + + SettingsEvents.emit('fetch-settings', settings); + return settings; + } + /* * Update a setting by id */ init(): void { this.initialLoad = true; SettingsModel.find().observe({ - added: (record: {_id: string; env: boolean; value: SettingValue}) => { - Meteor.settings[record._id] = record.value; - if (record.env === true) { - process.env[record._id] = String(record.value); - } - return this.load(record._id, record.value, this.initialLoad); - }, - changed: (record: {_id: string; env: boolean; value: SettingValue}) => { - Meteor.settings[record._id] = record.value; - if (record.env === true) { - process.env[record._id] = String(record.value); - } - return this.load(record._id, record.value, this.initialLoad); - }, - removed: (record: {_id: string; env: boolean}) => { - delete Meteor.settings[record._id]; - if (record.env === true) { - delete process.env[record._id]; - } - return this.load(record._id, undefined, this.initialLoad); - }, + added: (record: ISettingRecord) => this.storeSettingValue(record, this.initialLoad), + changed: (record: ISettingRecord) => this.storeSettingValue(record, this.initialLoad), + removed: (record: ISettingRecord) => this.removeSettingValue(record, this.initialLoad), }); this.initialLoad = false; this.afterInitialLoad.forEach((fn) => fn(Meteor.settings)); diff --git a/ee/app/canned-responses/server/settings.js b/ee/app/canned-responses/server/settings.js index 0b204f9cd614..d9b35f1d5d2f 100644 --- a/ee/app/canned-responses/server/settings.js +++ b/ee/app/canned-responses/server/settings.js @@ -6,6 +6,11 @@ export const createSettings = () => { this.add('Canned_Responses_Enable', false, { type: 'boolean', public: true, + enterprise: true, + invalidValue: false, + modules: [ + 'canned-responses', + ], }); }); }); diff --git a/ee/app/ldap-enterprise/server/settings.js b/ee/app/ldap-enterprise/server/settings.js index 9d5d5738b247..a9925a5ec361 100644 --- a/ee/app/ldap-enterprise/server/settings.js +++ b/ee/app/ldap-enterprise/server/settings.js @@ -7,23 +7,48 @@ export const createSettings = () => { this.add('LDAP_Enable_LDAP_Roles_To_RC_Roles', false, { type: 'boolean', enableQuery: { _id: 'LDAP_Enable', value: true }, + enterprise: true, + invalidValue: false, + modules: [ + 'ldap-enterprise', + ], }); this.add('LDAP_Roles_To_Rocket_Chat_Roles', '{}', { type: 'code', enableQuery: { _id: 'LDAP_Enable_LDAP_Roles_To_RC_Roles', value: true }, + enterprise: true, + invalidValue: '{}', + modules: [ + 'ldap-enterprise', + ], }); this.add('LDAP_Validate_Roles_For_Each_Login', false, { type: 'boolean', enableQuery: { _id: 'LDAP_Enable_LDAP_Roles_To_RC_Roles', value: true }, + enterprise: true, + invalidValue: false, + modules: [ + 'ldap-enterprise', + ], }); this.add('LDAP_Default_Role_To_User', 'user', { type: 'select', values: Roles.find({ scope: 'Users' }).fetch().map((role) => ({ key: role._id, i18nLabel: role._id })), enableQuery: { _id: 'LDAP_Enable_LDAP_Roles_To_RC_Roles', value: true }, + enterprise: true, + invalidValue: 'user', + modules: [ + 'ldap-enterprise', + ], }); this.add('LDAP_Query_To_Get_User_Groups', '(&(ou=*)(uniqueMember=uid=#{username},dc=example,dc=com))', { type: 'string', enableQuery: { _id: 'LDAP_Enable_LDAP_Roles_To_RC_Roles', value: true }, + enterprise: true, + invalidValue: '(&(ou=*)(uniqueMember=uid=#{username},dc=example,dc=com))', + modules: [ + 'ldap-enterprise', + ], }); }); @@ -37,6 +62,11 @@ export const createSettings = () => { ], i18nDescription: 'LDAP_Sync_User_Active_State_Description', enableQuery: { _id: 'LDAP_Enable', value: true }, + enterprise: true, + invalidValue: 'none', + modules: [ + 'ldap-enterprise', + ], }); }); }); diff --git a/ee/app/license/server/license.ts b/ee/app/license/server/license.ts index 6edb121c440a..9b3c6b81b8f6 100644 --- a/ee/app/license/server/license.ts +++ b/ee/app/license/server/license.ts @@ -144,6 +144,7 @@ class LicenseClass { return item; }); + EnterpriseLicenses.emit('validate'); this.showLicenses(); } @@ -228,6 +229,14 @@ export function onLicense(feature: string, cb: (...args: any[]) => void): void { EnterpriseLicenses.once(`valid:${ feature }`, cb); } +export function onValidateLicenses(cb: (...args: any[]) => void): void { + if (isEnterprise()) { + return cb(); + } + + EnterpriseLicenses.once('validate', cb); +} + export interface IOverrideClassProperties { [key: string]: (...args: any[]) => any; } diff --git a/ee/app/livechat-enterprise/server/settings.js b/ee/app/livechat-enterprise/server/settings.js index e2e92660ca2e..ee5b5aa41a6f 100644 --- a/ee/app/livechat-enterprise/server/settings.js +++ b/ee/app/livechat-enterprise/server/settings.js @@ -7,6 +7,8 @@ export const createSettings = () => { group: 'Omnichannel', section: 'Routing', i18nLabel: 'Waiting_queue', + enterprise: true, + invalidValue: false, }); settings.add('Livechat_waiting_queue_message', '', { @@ -16,6 +18,11 @@ export const createSettings = () => { i18nLabel: 'Waiting_queue_message', i18nDescription: 'Waiting_queue_message_description', enableQuery: { _id: 'Livechat_waiting_queue', value: true }, + enterprise: true, + invalidValue: '', + modules: [ + 'livechat-enterprise', + ], }); settings.add('Livechat_maximum_chats_per_agent', 0, { @@ -25,6 +32,11 @@ export const createSettings = () => { i18nLabel: 'Max_number_of_chats_per_agent', i18nDescription: 'Max_number_of_chats_per_agent_description', enableQuery: { _id: 'Livechat_waiting_queue', value: true }, + enterprise: true, + invalidValue: 0, + modules: [ + 'livechat-enterprise', + ], }); settings.add('Livechat_number_most_recent_chats_estimate_wait_time', 100, { @@ -34,6 +46,11 @@ export const createSettings = () => { i18nLabel: 'Number_of_most_recent_chats_estimate_wait_time', i18nDescription: 'Number_of_most_recent_chats_estimate_wait_time_description', enableQuery: { _id: 'Livechat_waiting_queue', value: true }, + enterprise: true, + invalidValue: 100, + modules: [ + 'livechat-enterprise', + ], }); settings.add('Livechat_auto_close_abandoned_rooms', false, { @@ -41,6 +58,11 @@ export const createSettings = () => { group: 'Omnichannel', section: 'Sessions', i18nLabel: 'Enable_omnichannel_auto_close_abandoned_rooms', + enterprise: true, + invalidValue: false, + modules: [ + 'livechat-enterprise', + ], }); settings.add('Livechat_abandoned_rooms_closed_custom_message', '', { @@ -49,6 +71,11 @@ export const createSettings = () => { section: 'Sessions', i18nLabel: 'Livechat_abandoned_rooms_closed_custom_message', enableQuery: { _id: 'Livechat_auto_close_abandoned_rooms', value: true }, + enterprise: true, + invalidValue: '', + modules: [ + 'livechat-enterprise', + ], }); settings.add('Livechat_last_chatted_agent_routing', false, { @@ -56,6 +83,11 @@ export const createSettings = () => { group: 'Omnichannel', section: 'Routing', enableQuery: { _id: 'Livechat_Routing_Method', value: { $ne: 'Manual_Selection' } }, + enterprise: true, + invalidValue: false, + modules: [ + 'livechat-enterprise', + ], }); settings.addGroup('Omnichannel', function() { @@ -71,10 +103,14 @@ export const createSettings = () => { }], public: true, i18nLabel: 'Livechat_business_hour_type', + enterprise: true, + invalidValue: 'Single', + modules: [ + 'livechat-enterprise', + ], }); }); }); - Settings.addOptionValueById('Livechat_Routing_Method', { key: 'Load_Balancing', i18nLabel: 'Load_Balancing' }); }; diff --git a/ee/app/settings/server/index.js b/ee/app/settings/server/index.js new file mode 100644 index 000000000000..97097791afdc --- /dev/null +++ b/ee/app/settings/server/index.js @@ -0,0 +1 @@ +import './settings'; diff --git a/ee/app/settings/server/settings.ts b/ee/app/settings/server/settings.ts new file mode 100644 index 000000000000..1f39fe08d88b --- /dev/null +++ b/ee/app/settings/server/settings.ts @@ -0,0 +1,46 @@ +import { SettingsEvents, settings } from '../../../../app/settings/server/functions/settings'; +import { SettingValue, ISettingRecord } from '../../../../app/settings/lib/settings'; +import { isEnterprise, hasLicense, onValidateLicenses } from '../../license/server/license'; +import SettingsModel from '../../../../app/models/server/models/Settings'; + +function getSettingValue(record: ISettingRecord): undefined | { value: SettingValue } { + if (!record.enterprise) { + return; + } + + if (!isEnterprise()) { + return { value: record.invalidValue }; + } + + if (!record.modules?.length) { + return; + } + + for (const moduleName of record.modules) { + if (!hasLicense(moduleName)) { + return { value: record.invalidValue }; + } + } +} + +SettingsEvents.on('store-setting-value', (record: ISettingRecord, newRecord: { value: SettingValue }) => { + const changedValue = getSettingValue(record); + if (changedValue) { + newRecord.value = changedValue.value; + } +}); + +SettingsEvents.on('fetch-settings', (settings: Array): void => { + for (const setting of settings) { + const changedValue = getSettingValue(setting); + if (changedValue) { + setting.value = changedValue.value; + } + } +}); + +onValidateLicenses(() => { + const enterpriseSettings = SettingsModel.findEnterpriseSettings(); + + enterpriseSettings.forEach((record: ISettingRecord) => settings.storeSettingValue(record, false)); +}); diff --git a/ee/client/index.js b/ee/client/index.js index d82fb5667c70..7eac07c7a681 100644 --- a/ee/client/index.js +++ b/ee/client/index.js @@ -4,3 +4,4 @@ import '../app/canned-responses/client/index'; import '../app/engagement-dashboard/client/index'; import '../app/license/client/index'; import '../app/livechat-enterprise/client/index'; +import '../app/settings/client/index'; diff --git a/ee/server/index.js b/ee/server/index.js index 0658aa609a40..b7aa74f4c07c 100644 --- a/ee/server/index.js +++ b/ee/server/index.js @@ -6,3 +6,4 @@ import '../app/canned-responses/server/index'; import '../app/engagement-dashboard/server/index'; import '../app/ldap-enterprise/server/index'; import '../app/livechat-enterprise/server/index'; +import '../app/settings/server/index'; diff --git a/server/publications/settings/index.js b/server/publications/settings/index.js index 81651a50bcbd..3f0249070e4b 100644 --- a/server/publications/settings/index.js +++ b/server/publications/settings/index.js @@ -4,6 +4,7 @@ import { Settings } from '../../../app/models/server'; import { Notifications } from '../../../app/notifications/server'; import { hasPermission, hasAtLeastOnePermission } from '../../../app/authorization/server'; import { getSettingPermissionId } from '../../../app/authorization/lib'; +import { SettingsEvents } from '../../../app/settings/server/functions/settings'; Meteor.methods({ 'public-settings/get'(updatedAt) { @@ -84,8 +85,13 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { value: setting.value, editor: setting.editor, properties: setting.properties, + enterprise: setting.enterprise, + invalidValue: setting.invalidValue, + modules: setting.modules, }; + SettingsEvents.emit('change-setting', setting, value); + if (setting.public === true) { Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, value); } From 1ac4ba79b03a40c85de886f11acaac05e4585e77 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 6 Jul 2020 20:09:48 -0300 Subject: [PATCH 02/18] Enterprise settings --- app/models/server/models/Settings.js | 4 +- app/settings/client/lib/settings.ts | 56 ++++------------------- app/settings/lib/settings.ts | 38 --------------- app/settings/server/functions/settings.ts | 41 ++++++++++++++++- ee/app/settings/server/settings.ts | 26 +++++++++-- ee/client/index.js | 1 - server/publications/settings/index.js | 13 ++++-- 7 files changed, 80 insertions(+), 99 deletions(-) diff --git a/app/models/server/models/Settings.js b/app/models/server/models/Settings.js index bd4ca7960897..2c766c24ca5b 100644 --- a/app/models/server/models/Settings.js +++ b/app/models/server/models/Settings.js @@ -58,7 +58,7 @@ export class Settings extends Base { filter._id = { $in: ids }; } - return this.find(filter, { fields: { _id: 1, value: 1, editor: 1 } }); + return this.find(filter, { fields: { _id: 1, value: 1, editor: 1, enterprise: 1, invalidValue: 1, modules: 1 } }); } findNotHiddenPublicUpdatedAfter(updatedAt) { @@ -70,7 +70,7 @@ export class Settings extends Base { }, }; - return this.find(filter, { fields: { _id: 1, value: 1, editor: 1 } }); + return this.find(filter, { fields: { _id: 1, value: 1, editor: 1, enterprise: 1, invalidValue: 1, modules: 1 } }); } findNotHiddenPrivate() { diff --git a/app/settings/client/lib/settings.ts b/app/settings/client/lib/settings.ts index 05945a9d95e9..c94664ae4d93 100644 --- a/app/settings/client/lib/settings.ts +++ b/app/settings/client/lib/settings.ts @@ -2,10 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { ReactiveDict } from 'meteor/reactive-dict'; import { PublicSettingsCachedCollection } from '../../../../client/lib/settings/PublicSettingsCachedCollection'; -import { SettingsBase, SettingValue, ISettingRecord } from '../../lib/settings'; -import { hasLicense } from '../../../../ee/app/license/client'; - -let isEnterprise = false; +import { SettingsBase, SettingValue } from '../../lib/settings'; class Settings extends SettingsBase { cachedCollection = PublicSettingsCachedCollection.get() @@ -18,43 +15,18 @@ class Settings extends SettingsBase { return this.dict.get(_id); } - getRecordValue(record: ISettingRecord): SettingValue { - const { value, invalidValue } = record; - - if (!record.enterprise) { - return value; - } - - if (!isEnterprise) { - return invalidValue; - } - - if (!record.modules?.length) { - return value; - } - - for (const moduleName of record.modules) { - if (!hasLicense(moduleName)) { - return invalidValue; - } - } - - return value; - } - - private _storeSettingValue(record: ISettingRecord, initialLoad: boolean): void { - const value = this.getRecordValue(record); - Meteor.settings[record._id] = value; - this.dict.set(record._id, value); - this.load(record._id, value, initialLoad); + private _storeSettingValue(record: { _id: string; value: SettingValue }, initialLoad: boolean): void { + Meteor.settings[record._id] = record.value; + this.dict.set(record._id, record.value); + this.load(record._id, record.value, initialLoad); } init(): void { let initialLoad = true; this.collection.find().observe({ - added: (record: ISettingRecord) => this._storeSettingValue(record, initialLoad), - changed: (record: ISettingRecord) => this._storeSettingValue(record, initialLoad), - removed: (record: ISettingRecord) => { + added: (record: { _id: string; value: SettingValue }) => this._storeSettingValue(record, initialLoad), + changed: (record: { _id: string; value: SettingValue }) => this._storeSettingValue(record, initialLoad), + removed: (record: { _id: string }) => { delete Meteor.settings[record._id]; this.dict.set(record._id, null); this.load(record._id, undefined, initialLoad); @@ -67,15 +39,3 @@ class Settings extends SettingsBase { export const settings = new Settings(); settings.init(); - -Meteor.startup(() => { - Meteor.call('license:isEnterprise', (err: any, result: any) => { - if (err) { - throw err; - } - - if (result) { - isEnterprise = true; - } - }); -}); diff --git a/app/settings/lib/settings.ts b/app/settings/lib/settings.ts index fe32a53340c8..4bc180c99415 100644 --- a/app/settings/lib/settings.ts +++ b/app/settings/lib/settings.ts @@ -6,44 +6,6 @@ export type SettingValueRoomPick = Array<{_id: string; name: string}> | string export type SettingValue = string | boolean | number | SettingValueMultiSelect | undefined; export type SettingComposedValue = {key: string; value: SettingValue}; export type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; -export interface ISettingAddOptions { - _id?: string; - type?: 'group' | 'boolean' | 'int' | 'string' | 'asset' | 'code' | 'select' | 'password' | 'action' | 'relativeUrl' | 'language' | 'date' | 'color' | 'font' | 'roomPick' | 'multiSelect'; - editor?: string; - packageEditor?: string; - packageValue?: SettingValue; - valueSource?: string; - hidden?: boolean; - blocked?: boolean; - secret?: boolean; - sorter?: number; - i18nLabel?: string; - i18nDescription?: string; - autocomplete?: boolean; - force?: boolean; - group?: string; - section?: string; - enableQuery?: any; - processEnvValue?: SettingValue; - meteorSettingsValue?: SettingValue; - value?: SettingValue; - ts?: Date; - multiline?: boolean; - values?: Array; - public?: boolean; - enterprise?: boolean; - modules?: Array; - invalidValue?: any; -} -export interface ISettingSelectOption { - key: string; - i18nLabel: string; -} -export interface ISettingRecord extends ISettingAddOptions { - _id: string; - env: boolean; - value: SettingValue; -} interface ISettingRegexCallbacks { regex: RegExp; diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts index 78c764905c1a..72f62c97ac26 100644 --- a/app/settings/server/functions/settings.ts +++ b/app/settings/server/functions/settings.ts @@ -3,7 +3,7 @@ import { EventEmitter } from 'events'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { SettingsBase, SettingValue, ISettingAddOptions, ISettingRecord } from '../../lib/settings'; +import { SettingsBase, SettingValue } from '../../lib/settings'; import SettingsModel from '../../../models/server/models/Settings'; const blockedSettings = new Set(); @@ -58,6 +58,45 @@ const overrideSetting = (_id: string, value: SettingValue, options: ISettingAddO return value; }; +export interface ISettingAddOptions { + _id?: string; + type?: 'group' | 'boolean' | 'int' | 'string' | 'asset' | 'code' | 'select' | 'password' | 'action' | 'relativeUrl' | 'language' | 'date' | 'color' | 'font' | 'roomPick' | 'multiSelect'; + editor?: string; + packageEditor?: string; + packageValue?: SettingValue; + valueSource?: string; + hidden?: boolean; + blocked?: boolean; + secret?: boolean; + sorter?: number; + i18nLabel?: string; + i18nDescription?: string; + autocomplete?: boolean; + force?: boolean; + group?: string; + section?: string; + enableQuery?: any; + processEnvValue?: SettingValue; + meteorSettingsValue?: SettingValue; + value?: SettingValue; + ts?: Date; + multiline?: boolean; + values?: Array; + public?: boolean; + enterprise?: boolean; + modules?: Array; + invalidValue?: any; +} +export interface ISettingSelectOption { + key: string; + i18nLabel: string; +} +export interface ISettingRecord extends ISettingAddOptions { + _id: string; + env: boolean; + value: SettingValue; +} + export interface ISettingAddGroupOptions { hidden?: boolean; blocked?: boolean; diff --git a/ee/app/settings/server/settings.ts b/ee/app/settings/server/settings.ts index 1f39fe08d88b..fb7f655f3962 100644 --- a/ee/app/settings/server/settings.ts +++ b/ee/app/settings/server/settings.ts @@ -1,9 +1,9 @@ -import { SettingsEvents, settings } from '../../../../app/settings/server/functions/settings'; -import { SettingValue, ISettingRecord } from '../../../../app/settings/lib/settings'; +import { SettingsEvents, settings, ISettingRecord } from '../../../../app/settings/server/functions/settings'; +import { SettingValue } from '../../../../app/settings/lib/settings'; import { isEnterprise, hasLicense, onValidateLicenses } from '../../license/server/license'; import SettingsModel from '../../../../app/models/server/models/Settings'; -function getSettingValue(record: ISettingRecord): undefined | { value: SettingValue } { +function changeSettingValue(record: ISettingRecord): undefined | { value: SettingValue } { if (!record.enterprise) { return; } @@ -24,7 +24,7 @@ function getSettingValue(record: ISettingRecord): undefined | { value: SettingVa } SettingsEvents.on('store-setting-value', (record: ISettingRecord, newRecord: { value: SettingValue }) => { - const changedValue = getSettingValue(record); + const changedValue = changeSettingValue(record); if (changedValue) { newRecord.value = changedValue.value; } @@ -32,13 +32,29 @@ SettingsEvents.on('store-setting-value', (record: ISettingRecord, newRecord: { v SettingsEvents.on('fetch-settings', (settings: Array): void => { for (const setting of settings) { - const changedValue = getSettingValue(setting); + const changedValue = changeSettingValue(setting); if (changedValue) { setting.value = changedValue.value; } } }); +type ISettingNotificationValue = { + _id: string; + value: SettingValue; + editor: string; + properties: string; + enterprise: boolean; +}; + +SettingsEvents.on('change-setting', (record: ISettingRecord, value: ISettingNotificationValue): void => { + const changedValue = changeSettingValue(record); + if (changedValue) { + value.value = changedValue.value; + } +}); + + onValidateLicenses(() => { const enterpriseSettings = SettingsModel.findEnterpriseSettings(); diff --git a/ee/client/index.js b/ee/client/index.js index 7eac07c7a681..d82fb5667c70 100644 --- a/ee/client/index.js +++ b/ee/client/index.js @@ -4,4 +4,3 @@ import '../app/canned-responses/client/index'; import '../app/engagement-dashboard/client/index'; import '../app/license/client/index'; import '../app/livechat-enterprise/client/index'; -import '../app/settings/client/index'; diff --git a/server/publications/settings/index.js b/server/publications/settings/index.js index 3f0249070e4b..4c941d8fe006 100644 --- a/server/publications/settings/index.js +++ b/server/publications/settings/index.js @@ -8,10 +8,13 @@ import { SettingsEvents } from '../../../app/settings/server/functions/settings' Meteor.methods({ 'public-settings/get'(updatedAt) { + const mapFn = ({ _id, value, editor }) => ({ _id, value, editor }); if (updatedAt instanceof Date) { const records = Settings.findNotHiddenPublicUpdatedAfter(updatedAt).fetch(); + SettingsEvents.emit('fetch-settings', records); + return { - update: records, + update: records.map(mapFn), remove: Settings.trashFindDeletedAfter(updatedAt, { hidden: { $ne: true, @@ -25,7 +28,11 @@ Meteor.methods({ }).fetch(), }; } - return Settings.findNotHiddenPublic().fetch(); + + const publicSettings = Settings.findNotHiddenPublic().fetch(); + SettingsEvents.emit('fetch-settings', publicSettings); + + return publicSettings.map(mapFn); }, 'private-settings/get'(updatedAfter) { const uid = Meteor.userId(); @@ -86,8 +93,6 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { editor: setting.editor, properties: setting.properties, enterprise: setting.enterprise, - invalidValue: setting.invalidValue, - modules: setting.modules, }; SettingsEvents.emit('change-setting', setting, value); From e1a5656279cf7bcfeaff48be9ba80ae9bed0a35e Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 6 Jul 2020 20:28:47 -0300 Subject: [PATCH 03/18] Fixed api calls to include the enterprise fields on the settings query even if the client doesn't request them. --- app/api/server/v1/settings.js | 34 ++++++++++++++--------- app/settings/server/functions/settings.ts | 10 ------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.js index 930f76be5c3c..552898357343 100644 --- a/app/api/server/v1/settings.js +++ b/app/api/server/v1/settings.js @@ -6,7 +6,25 @@ import _ from 'underscore'; import { Settings } from '../../../models/server'; import { hasPermission } from '../../../authorization'; import { API } from '../api'; -import { settings as settingsLib } from '../../../settings/server'; +import { SettingsEvents } from '../../../settings/server'; + +const fetchSettings = (query, sort, offset, count, fields) => { + const settings = Settings.find.fetchSettings(query, { + sort: sort || { _id: 1 }, + skip: offset, + limit: count, + fields: Object.assign({ _id: 1, value: 1, enterprise: 1, invalidValue: 1, modules: 1 }, fields), + }); + + SettingsEvents.emit('fetch-settings', settings); + return settings.map((setting) => { + 'enterprise' in setting && !fields.enterprise && delete setting.enterprise; + 'invalidValue' in setting && !fields.invalidValue && delete setting.invalidValue; + 'modules' in setting && !fields.modules && delete setting.modules; + + return setting; + }); +}; // settings endpoints API.v1.addRoute('settings.public', { authRequired: false }, { @@ -21,12 +39,7 @@ API.v1.addRoute('settings.public', { authRequired: false }, { ourQuery = Object.assign({}, query, ourQuery); - const settings = settingsLib.fetchSettings(ourQuery, { - sort: sort || { _id: 1 }, - skip: offset, - limit: count, - fields: Object.assign({ _id: 1, value: 1 }, fields), - }); + const settings = fetchSettings(ourQuery, sort, offset, count, fields); return API.v1.success({ settings, @@ -95,12 +108,7 @@ API.v1.addRoute('settings', { authRequired: true }, { ourQuery = Object.assign({}, query, ourQuery); - const settings = settingsLib.fetchSettings(ourQuery, { - sort: sort || { _id: 1 }, - skip: offset, - limit: count, - fields: Object.assign({ _id: 1, value: 1 }, fields), - }); + const settings = fetchSettings(ourQuery, sort, offset, count, fields); return API.v1.success({ settings, diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts index 72f62c97ac26..544dbcc91009 100644 --- a/app/settings/server/functions/settings.ts +++ b/app/settings/server/functions/settings.ts @@ -380,16 +380,6 @@ class Settings extends SettingsBase { this.load(record._id, undefined, initialLoad); } - /* - * Runs a query on the settings module and pass the fetched data through an event before returning - */ - fetchSettings(query: Record, options: Record): void { - const settings = SettingsModel.find(query, options).fetch(); - - SettingsEvents.emit('fetch-settings', settings); - return settings; - } - /* * Update a setting by id */ From 3ddc2995751fd34bd7c4ad151b82891eb0b8dcde Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Wed, 8 Jul 2020 21:31:42 -0300 Subject: [PATCH 04/18] Fixed initial value of enterprise settings --- app/settings/server/functions/settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts index 544dbcc91009..6c6d81e28df2 100644 --- a/app/settings/server/functions/settings.ts +++ b/app/settings/server/functions/settings.ts @@ -353,7 +353,7 @@ class Settings extends SettingsBase { */ storeSettingValue(record: ISettingRecord, initialLoad: boolean): void { const newData = { - value: record.enterprise ? record.invalidValue : record.value, + value: record.value, }; SettingsEvents.emit('store-setting-value', record, newData); const { value } = newData; From faeffbd753d672bb6ae00fc56c57e7e5f7901ebc Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Wed, 8 Jul 2020 21:35:25 -0300 Subject: [PATCH 05/18] Added setting to only send the message id on push notifications --- app/lib/server/startup/settings.js | 10 +++++++++- app/push-notifications/server/lib/PushNotification.js | 4 +++- packages/rocketchat-i18n/i18n/en.i18n.json | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/lib/server/startup/settings.js b/app/lib/server/startup/settings.js index 9a063480c7e1..6caae5989b72 100644 --- a/app/lib/server/startup/settings.js +++ b/app/lib/server/startup/settings.js @@ -1283,10 +1283,18 @@ settings.addGroup('Push', function() { type: 'boolean', public: true, }); - return this.add('Push_show_message', true, { + this.add('Push_show_message', true, { type: 'boolean', public: true, }); + return this.add('Push_request_content_from_server', true, { + type: 'boolean', + enterprise: true, + invalidValue: false, + modules: [ + 'push-privacy', + ], + }); }); }); diff --git a/app/push-notifications/server/lib/PushNotification.js b/app/push-notifications/server/lib/PushNotification.js index 76847b6f4c3c..a0348ccc90ba 100644 --- a/app/push-notifications/server/lib/PushNotification.js +++ b/app/push-notifications/server/lib/PushNotification.js @@ -31,6 +31,7 @@ export class PushNotification { title = `${ username }`; } + const idOnly = settings.get('Push_request_content_from_server'); const config = { from: 'push', badge, @@ -42,7 +43,8 @@ export class PushNotification { host: Meteor.absoluteUrl(), rid, messageId, - ...payload, + notificationType: idOnly ? 'message-id-only' : 'message', + ...idOnly ? { } : payload, }, userId, notId: this.getNotificationId(rid), diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index f7f93ee7cca6..3c857779b699 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -2859,6 +2859,7 @@ "Push_gcm_api_key": "GCM API Key", "Push_gcm_project_number": "GCM Project Number", "Push_production": "Production", + "Push_request_content_from_server": "Fetch full message content from the server on receipt", "Push_show_message": "Show Message in Notification", "Push_show_username_room": "Show Channel/Group/Username in Notification", "Push_test_push": "Test", From 79bc9c57d8eccb6eba25cb6a3a98ecaf0bfd4b3e Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 13 Jul 2020 15:53:15 -0300 Subject: [PATCH 06/18] Add new endpoint to receive the notification data --- app/api/server/v1/push.js | 16 ++++ .../server/lib/PushNotification.js | 81 ++++++++++++++++--- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/app/api/server/v1/push.js b/app/api/server/v1/push.js index 9e3c8d59b325..765e6a3ab560 100644 --- a/app/api/server/v1/push.js +++ b/app/api/server/v1/push.js @@ -1,8 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; +import { Match, check } from 'meteor/check'; import { appTokensCollection } from '../../../push/server'; import { API } from '../api'; +import PushNotification from '../../../push-notifications/server/lib/PushNotification'; + API.v1.addRoute('push.token', { authRequired: true }, { post() { @@ -63,3 +66,16 @@ API.v1.addRoute('push.token', { authRequired: true }, { return API.v1.success(); }, }); + +API.v1.addRoute('push.get', { authRequired: true }, { + get() { + const params = this.requestParams(); + check(params, Match.ObjectIncluding({ + id: String, + })); + + const data = PushNotification.getNotificationForMessageId(params.id, Meteor.userId()); + + return API.v1.success({ data }); + }, +}); diff --git a/app/push-notifications/server/lib/PushNotification.js b/app/push-notifications/server/lib/PushNotification.js index a0348ccc90ba..117ee00f4b9e 100644 --- a/app/push-notifications/server/lib/PushNotification.js +++ b/app/push-notifications/server/lib/PushNotification.js @@ -3,7 +3,11 @@ import { Meteor } from 'meteor/meteor'; import { Push } from '../../../push/server'; import { settings } from '../../../settings/server'; import { metrics } from '../../../metrics/server'; +import { Messages, Rooms, Users } from '../../../models/server'; import { RocketChatAssets } from '../../../assets/server'; +import { replaceMentionedUsernamesWithFullNames, parseMessageTextPerUser } from '../../../lib/server/functions/notifications'; +import { callbacks } from '../../../callbacks/server'; +import { getPushData } from '../../../lib/server/functions/notifications/mobile'; export class PushNotification { getNotificationId(roomId) { @@ -11,18 +15,7 @@ export class PushNotification { return this.hash(`${ serverId }|${ roomId }`); // hash } - hash(str) { - let hash = 0; - let i = str.length; - - while (i) { - hash = ((hash << 5) - hash) + str.charCodeAt(--i); - hash &= hash; // Convert to 32bit integer - } - return hash; - } - - send({ rid, uid: userId, mid: messageId, roomName, username, message, payload, badge = 1, category }) { + getNotificationConfig({ rid, uid: userId, mid: messageId, roomName, username, message, payload, badge = 1, category, idOnly = false }) { let title; if (roomName && roomName !== '') { title = `${ roomName }`; @@ -31,7 +24,6 @@ export class PushNotification { title = `${ username }`; } - const idOnly = settings.get('Push_request_content_from_server'); const config = { from: 'push', badge, @@ -60,9 +52,72 @@ export class PushNotification { }; } + return config; + } + + hash(str) { + let hash = 0; + let i = str.length; + + while (i) { + hash = ((hash << 5) - hash) + str.charCodeAt(--i); + hash &= hash; // Convert to 32bit integer + } + return hash; + } + + send({ rid, uid: userId, mid: messageId, roomName, username, message, payload, badge = 1, category }) { + const idOnly = settings.get('Push_request_content_from_server'); + const config = this.getNotificationConfig({ rid, userId, messageId, roomName, username, message, payload, badge, category, idOnly }); + metrics.notificationsSent.inc({ notification_type: 'mobile' }); return Push.send(config); } + + getNotificationForMessageId(messageId, receiverUserId) { + const message = Messages.findOneById(messageId); + if (!message) { + throw new Error('Message not found'); + } + + const receiver = Users.findOne(receiverUserId); + if (!receiver) { + throw new Error('User not found'); + } + + const sender = Users.findOne(message.u._id, { fields: { username: 1, name: 1 } }); + if (!sender) { + throw new Error('Message sender not found'); + } + + let notificationMessage = callbacks.run('beforeSendMessageNotifications', message.msg); + if (message.mentions?.length > 0 && settings.get('UI_Use_Real_Name')) { + notificationMessage = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions); + } + notificationMessage = parseMessageTextPerUser(notificationMessage, message, receiver); + + const room = Rooms.findOneById(message.rid); + const pushData = Promise.await(getPushData({ + room, + message, + userId: receiverUserId, + receiverUsername: receiver.username, + senderUsername: sender.username, + senderName: sender.name, + notificationMessage, + })); + + return { + message, + notification: this.getNotificationConfig({ + ...pushData, + rid: message.rid, + uid: message.u._id, + mid: messageId, + idOnly: false, + }), + }; + } } export default new PushNotification(); From 41fb87d507a40ebc4bd4f98b8d72b54e177c851a Mon Sep 17 00:00:00 2001 From: pierre-lehnen-rc <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Tue, 14 Jul 2020 18:11:36 -0300 Subject: [PATCH 07/18] Update app/settings/server/functions/settings.ts Co-authored-by: Rodrigo Nascimento --- app/settings/server/functions/settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts index 6c6d81e28df2..480c2f936832 100644 --- a/app/settings/server/functions/settings.ts +++ b/app/settings/server/functions/settings.ts @@ -85,7 +85,7 @@ export interface ISettingAddOptions { public?: boolean; enterprise?: boolean; modules?: Array; - invalidValue?: any; + invalidValue?: SettingValue; } export interface ISettingSelectOption { key: string; From 60d2a326e378880130c8a61257cd0f0c2b1365f7 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 14 Jul 2020 18:13:40 -0300 Subject: [PATCH 08/18] Fixed argument names --- app/push-notifications/server/lib/PushNotification.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/push-notifications/server/lib/PushNotification.js b/app/push-notifications/server/lib/PushNotification.js index 117ee00f4b9e..e903cccad6b9 100644 --- a/app/push-notifications/server/lib/PushNotification.js +++ b/app/push-notifications/server/lib/PushNotification.js @@ -66,9 +66,9 @@ export class PushNotification { return hash; } - send({ rid, uid: userId, mid: messageId, roomName, username, message, payload, badge = 1, category }) { + send({ rid, uid, mid, roomName, username, message, payload, badge = 1, category }) { const idOnly = settings.get('Push_request_content_from_server'); - const config = this.getNotificationConfig({ rid, userId, messageId, roomName, username, message, payload, badge, category, idOnly }); + const config = this.getNotificationConfig({ rid, uid, mid, roomName, username, message, payload, badge, category, idOnly }); metrics.notificationsSent.inc({ notification_type: 'mobile' }); return Push.send(config); From 00a48a2dd17843a3f8f5eaaaaa5517ecedcc31c6 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Tue, 14 Jul 2020 18:15:54 -0300 Subject: [PATCH 09/18] Return settings' enterprise info on public-settings/get --- server/publications/settings/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/publications/settings/index.js b/server/publications/settings/index.js index 4c941d8fe006..7b379059b50b 100644 --- a/server/publications/settings/index.js +++ b/server/publications/settings/index.js @@ -8,13 +8,12 @@ import { SettingsEvents } from '../../../app/settings/server/functions/settings' Meteor.methods({ 'public-settings/get'(updatedAt) { - const mapFn = ({ _id, value, editor }) => ({ _id, value, editor }); if (updatedAt instanceof Date) { const records = Settings.findNotHiddenPublicUpdatedAfter(updatedAt).fetch(); SettingsEvents.emit('fetch-settings', records); return { - update: records.map(mapFn), + update: records, remove: Settings.trashFindDeletedAfter(updatedAt, { hidden: { $ne: true, @@ -32,7 +31,7 @@ Meteor.methods({ const publicSettings = Settings.findNotHiddenPublic().fetch(); SettingsEvents.emit('fetch-settings', publicSettings); - return publicSettings.map(mapFn); + return publicSettings; }, 'private-settings/get'(updatedAfter) { const uid = Meteor.userId(); From 894fddc51eb5c8e70cba31b0a62e88fc73d35a04 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Wed, 15 Jul 2020 15:06:17 -0300 Subject: [PATCH 10/18] Fixed broken code --- app/api/server/v1/settings.js | 4 ++-- app/settings/server/index.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.js index 552898357343..3c579e33f630 100644 --- a/app/api/server/v1/settings.js +++ b/app/api/server/v1/settings.js @@ -9,12 +9,12 @@ import { API } from '../api'; import { SettingsEvents } from '../../../settings/server'; const fetchSettings = (query, sort, offset, count, fields) => { - const settings = Settings.find.fetchSettings(query, { + const settings = Settings.find(query, { sort: sort || { _id: 1 }, skip: offset, limit: count, fields: Object.assign({ _id: 1, value: 1, enterprise: 1, invalidValue: 1, modules: 1 }, fields), - }); + }).fetch(); SettingsEvents.emit('fetch-settings', settings); return settings.map((setting) => { diff --git a/app/settings/server/index.ts b/app/settings/server/index.ts index edcce3a33231..7a4f6ebf00b4 100644 --- a/app/settings/server/index.ts +++ b/app/settings/server/index.ts @@ -1,6 +1,7 @@ -import { settings } from './functions/settings'; +import { settings, SettingsEvents } from './functions/settings'; import './observer'; export { settings, + SettingsEvents, }; From e7172fdbb561f2f4cfa981b8d2aa4a875e8aefdc Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Wed, 15 Jul 2020 19:30:36 -0300 Subject: [PATCH 11/18] Fixed some issues with license loading --- ee/app/license/server/license.ts | 2 +- ee/app/settings/server/settings.ts | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ee/app/license/server/license.ts b/ee/app/license/server/license.ts index 9b3c6b81b8f6..67086fe6f287 100644 --- a/ee/app/license/server/license.ts +++ b/ee/app/license/server/license.ts @@ -234,7 +234,7 @@ export function onValidateLicenses(cb: (...args: any[]) => void): void { return cb(); } - EnterpriseLicenses.once('validate', cb); + EnterpriseLicenses.on('validate', cb); } export interface IOverrideClassProperties { diff --git a/ee/app/settings/server/settings.ts b/ee/app/settings/server/settings.ts index fb7f655f3962..df8f3d5662e9 100644 --- a/ee/app/settings/server/settings.ts +++ b/ee/app/settings/server/settings.ts @@ -1,3 +1,5 @@ +import { Meteor } from 'meteor/meteor'; + import { SettingsEvents, settings, ISettingRecord } from '../../../../app/settings/server/functions/settings'; import { SettingValue } from '../../../../app/settings/lib/settings'; import { isEnterprise, hasLicense, onValidateLicenses } from '../../license/server/license'; @@ -54,9 +56,18 @@ SettingsEvents.on('change-setting', (record: ISettingRecord, value: ISettingNoti } }); - -onValidateLicenses(() => { +function updateSettings(): void { const enterpriseSettings = SettingsModel.findEnterpriseSettings(); enterpriseSettings.forEach((record: ISettingRecord) => settings.storeSettingValue(record, false)); +} + + +Meteor.startup(() => { + updateSettings(); + + // If there was no license loaded, add a callback to update the settings once one is added + if (!isEnterprise()) { + onValidateLicenses(updateSettings); + } }); From 2b83da3db13b51761e28e816b2e5345a976f5222 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Thu, 16 Jul 2020 12:47:49 -0300 Subject: [PATCH 12/18] Update settings if a license becomes invalid --- ee/app/license/server/license.ts | 4 ---- ee/app/settings/server/settings.ts | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/ee/app/license/server/license.ts b/ee/app/license/server/license.ts index 67086fe6f287..6a3436f8d03a 100644 --- a/ee/app/license/server/license.ts +++ b/ee/app/license/server/license.ts @@ -230,10 +230,6 @@ export function onLicense(feature: string, cb: (...args: any[]) => void): void { } export function onValidateLicenses(cb: (...args: any[]) => void): void { - if (isEnterprise()) { - return cb(); - } - EnterpriseLicenses.on('validate', cb); } diff --git a/ee/app/settings/server/settings.ts b/ee/app/settings/server/settings.ts index df8f3d5662e9..560d057c5f3e 100644 --- a/ee/app/settings/server/settings.ts +++ b/ee/app/settings/server/settings.ts @@ -66,8 +66,5 @@ function updateSettings(): void { Meteor.startup(() => { updateSettings(); - // If there was no license loaded, add a callback to update the settings once one is added - if (!isEnterprise()) { - onValidateLicenses(updateSettings); - } + onValidateLicenses(updateSettings); }); From aed77875aec848b7cd6d12475a4cae7e2a2629cf Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 20 Jul 2020 15:58:51 -0300 Subject: [PATCH 13/18] Code quality --- app/lib/server/startup/settings.js | 2 +- app/push-notifications/server/lib/PushNotification.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/server/startup/settings.js b/app/lib/server/startup/settings.js index 6caae5989b72..bc5b0217ea89 100644 --- a/app/lib/server/startup/settings.js +++ b/app/lib/server/startup/settings.js @@ -1287,7 +1287,7 @@ settings.addGroup('Push', function() { type: 'boolean', public: true, }); - return this.add('Push_request_content_from_server', true, { + this.add('Push_request_content_from_server', true, { type: 'boolean', enterprise: true, invalidValue: false, diff --git a/app/push-notifications/server/lib/PushNotification.js b/app/push-notifications/server/lib/PushNotification.js index e903cccad6b9..d114acc0aae0 100644 --- a/app/push-notifications/server/lib/PushNotification.js +++ b/app/push-notifications/server/lib/PushNotification.js @@ -36,7 +36,7 @@ export class PushNotification { rid, messageId, notificationType: idOnly ? 'message-id-only' : 'message', - ...idOnly ? { } : payload, + ...idOnly || payload, }, userId, notId: this.getNotificationId(rid), From 3aa905496f95b9e4549fceade7b88af687dd88b0 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 20 Jul 2020 15:59:01 -0300 Subject: [PATCH 14/18] Add push-privacy to list of modules --- ee/app/license/server/bundles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ee/app/license/server/bundles.ts b/ee/app/license/server/bundles.ts index 5b92f305a1bd..e984befa19df 100644 --- a/ee/app/license/server/bundles.ts +++ b/ee/app/license/server/bundles.ts @@ -9,6 +9,7 @@ const bundles: IBundle = { 'ldap-enterprise', 'livechat-enterprise', 'engagement-dashboard', + 'push-privacy', ], pro: [ ], From 1c6dd7ea86606879c59879a5039982bc12bc8bf4 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 20 Jul 2020 15:59:13 -0300 Subject: [PATCH 15/18] Skip some extra data when using idOnly --- app/push-notifications/server/lib/PushNotification.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/push-notifications/server/lib/PushNotification.js b/app/push-notifications/server/lib/PushNotification.js index d114acc0aae0..5846bc858ed6 100644 --- a/app/push-notifications/server/lib/PushNotification.js +++ b/app/push-notifications/server/lib/PushNotification.js @@ -29,11 +29,11 @@ export class PushNotification { badge, sound: 'default', priority: 10, - title, - text: message, + title: idOnly ? '' : title, + text: idOnly ? '' : message, payload: { host: Meteor.absoluteUrl(), - rid, + ...idOnly || { rid }, messageId, notificationType: idOnly ? 'message-id-only' : 'message', ...idOnly || payload, From a07e57bbf4db4d846d8f417ea97f182743b4c59a Mon Sep 17 00:00:00 2001 From: Pierre Lehnen Date: Mon, 20 Jul 2020 16:02:24 -0300 Subject: [PATCH 16/18] Validate if the user can access the room before loading the message data --- app/api/server/v1/push.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/api/server/v1/push.js b/app/api/server/v1/push.js index 765e6a3ab560..264fabba87be 100644 --- a/app/api/server/v1/push.js +++ b/app/api/server/v1/push.js @@ -5,7 +5,8 @@ import { Match, check } from 'meteor/check'; import { appTokensCollection } from '../../../push/server'; import { API } from '../api'; import PushNotification from '../../../push-notifications/server/lib/PushNotification'; - +import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom'; +import { Users, Messages, Rooms } from '../../../models/server'; API.v1.addRoute('push.token', { authRequired: true }, { post() { @@ -74,6 +75,25 @@ API.v1.addRoute('push.get', { authRequired: true }, { id: String, })); + const message = Messages.findOneById(params.id, { fields: { rid: 1 } }); + if (!message) { + throw new Error('error-message-not-found'); + } + + const user = Users.findOneById(Meteor.userId()); + if (!user) { + throw new Error('error-user-not-found'); + } + + const room = Rooms.findOneById(message.rid); + if (!user) { + throw new Error('error-room-not-found'); + } + + if (!canAccessRoom(room, user)) { + throw new Error('error-room-not-found'); + } + const data = PushNotification.getNotificationForMessageId(params.id, Meteor.userId()); return API.v1.success({ data }); From 83eded7a778f42d97b9e50a47cafc5f3822f533c Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 20 Jul 2020 23:06:46 -0300 Subject: [PATCH 17/18] Always return enterprise fields --- app/api/server/v1/settings.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.js index 3c579e33f630..6cad33dca4be 100644 --- a/app/api/server/v1/settings.js +++ b/app/api/server/v1/settings.js @@ -17,13 +17,7 @@ const fetchSettings = (query, sort, offset, count, fields) => { }).fetch(); SettingsEvents.emit('fetch-settings', settings); - return settings.map((setting) => { - 'enterprise' in setting && !fields.enterprise && delete setting.enterprise; - 'invalidValue' in setting && !fields.invalidValue && delete setting.invalidValue; - 'modules' in setting && !fields.modules && delete setting.modules; - - return setting; - }); + return settings; }; // settings endpoints From 0e2c449fd6d39138dca8065dbdbbab1a1e21a76d Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 21 Jul 2020 00:09:01 -0300 Subject: [PATCH 18/18] Improve checkins --- app/api/server/v1/push.js | 20 +++++++++---------- .../server/lib/PushNotification.js | 19 ++++-------------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/app/api/server/v1/push.js b/app/api/server/v1/push.js index 264fabba87be..937b62efc9d7 100644 --- a/app/api/server/v1/push.js +++ b/app/api/server/v1/push.js @@ -75,26 +75,26 @@ API.v1.addRoute('push.get', { authRequired: true }, { id: String, })); - const message = Messages.findOneById(params.id, { fields: { rid: 1 } }); - if (!message) { - throw new Error('error-message-not-found'); + const receiver = Users.findOneById(this.userId); + if (!receiver) { + throw new Error('error-user-not-found'); } - const user = Users.findOneById(Meteor.userId()); - if (!user) { - throw new Error('error-user-not-found'); + const message = Messages.findOneById(params.id); + if (!message) { + throw new Error('error-message-not-found'); } const room = Rooms.findOneById(message.rid); - if (!user) { + if (!room) { throw new Error('error-room-not-found'); } - if (!canAccessRoom(room, user)) { - throw new Error('error-room-not-found'); + if (!canAccessRoom(room, receiver)) { + throw new Error('error-not-allowed'); } - const data = PushNotification.getNotificationForMessageId(params.id, Meteor.userId()); + const data = PushNotification.getNotificationForMessageId({ receiver, room, message }); return API.v1.success({ data }); }, diff --git a/app/push-notifications/server/lib/PushNotification.js b/app/push-notifications/server/lib/PushNotification.js index 5846bc858ed6..dbbc587e69fe 100644 --- a/app/push-notifications/server/lib/PushNotification.js +++ b/app/push-notifications/server/lib/PushNotification.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { Push } from '../../../push/server'; import { settings } from '../../../settings/server'; import { metrics } from '../../../metrics/server'; -import { Messages, Rooms, Users } from '../../../models/server'; +import { Users } from '../../../models/server'; import { RocketChatAssets } from '../../../assets/server'; import { replaceMentionedUsernamesWithFullNames, parseMessageTextPerUser } from '../../../lib/server/functions/notifications'; import { callbacks } from '../../../callbacks/server'; @@ -74,17 +74,7 @@ export class PushNotification { return Push.send(config); } - getNotificationForMessageId(messageId, receiverUserId) { - const message = Messages.findOneById(messageId); - if (!message) { - throw new Error('Message not found'); - } - - const receiver = Users.findOne(receiverUserId); - if (!receiver) { - throw new Error('User not found'); - } - + getNotificationForMessageId({ receiver, message, room }) { const sender = Users.findOne(message.u._id, { fields: { username: 1, name: 1 } }); if (!sender) { throw new Error('Message sender not found'); @@ -96,11 +86,10 @@ export class PushNotification { } notificationMessage = parseMessageTextPerUser(notificationMessage, message, receiver); - const room = Rooms.findOneById(message.rid); const pushData = Promise.await(getPushData({ room, message, - userId: receiverUserId, + userId: receiver._id, receiverUsername: receiver.username, senderUsername: sender.username, senderName: sender.name, @@ -113,7 +102,7 @@ export class PushNotification { ...pushData, rid: message.rid, uid: message.u._id, - mid: messageId, + mid: message._id, idOnly: false, }), };