Regression: Overwrite model functions on EE only when license applied (#17061)

* Overwrite model function on EE only license applied

* Convert to typescript

Co-authored-by: Rodrigo Nascimento <rodrigoknascimento@gmail.com>
pull/17070/head
Diego Sampaio 6 years ago committed by GitHub
parent af3d8883b8
commit 03cb0e1edd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      ee/app/license/server/bundles.ts
  2. 4
      ee/app/license/server/decrypt.ts
  3. 6
      ee/app/license/server/index.js
  4. 5
      ee/app/license/server/index.ts
  5. 118
      ee/app/license/server/license.ts
  6. 5
      ee/app/license/server/methods.ts
  7. 1
      ee/app/livechat-enterprise/server/lib/units.js
  8. 79
      ee/app/models/server/models/LivechatDepartment.js
  9. 50
      ee/app/models/server/models/LivechatRooms.js
  10. 35
      ee/app/models/server/raw/LivechatDepartment.js
  11. 42
      ee/app/models/server/raw/LivechatRooms.js

@ -1,4 +1,8 @@
const bundles = {
interface IBundle {
[key: string]: string[];
}
const bundles: IBundle = {
enterprise: [
'auditing',
'canned-responses',
@ -10,7 +14,7 @@ const bundles = {
],
};
const getBundleFromModule = (moduleName) => {
const getBundleFromModule = (moduleName: string): string|undefined => {
const match = moduleName.match(/(.*):\*$/);
if (!match) {
return;
@ -19,7 +23,7 @@ const getBundleFromModule = (moduleName) => {
return match[1];
};
export function isBundle(moduleName) {
export function isBundle(moduleName: string): boolean {
if (moduleName === '*') {
return true;
}
@ -32,10 +36,10 @@ export function isBundle(moduleName) {
return true;
}
export function getBundleModules(moduleName) {
export function getBundleModules(moduleName: string): string[] {
if (moduleName === '*') {
return Object.keys(bundles)
.reduce((modules, bundle) => modules.concat(bundles[bundle]), []);
.reduce<string[]>((modules, bundle) => modules.concat(bundles[bundle]), []);
}
const bundle = getBundleFromModule(moduleName);

@ -2,8 +2,8 @@ import crypto from 'crypto';
const publicKey = 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFxV1Nza2Q5LzZ6Ung4a3lQY2ljcwpiMzJ3Mnd4VnV3N3lCVDk2clEvOEQreU1lQ01POXdTU3BIYS85bkZ5d293RXRpZ3B0L3dyb1BOK1ZHU3didHdQCkZYQmVxRWxCbmRHRkFsODZlNStFbGlIOEt6L2hHbkNtSk5tWHB4RUsyUkUwM1g0SXhzWVg3RERCN010eC9pcXMKY2pCL091dlNCa2ppU2xlUzdibE5JVC9kQTdLNC9DSjNvaXUwMmJMNEV4Y2xDSGVwenFOTWVQM3dVWmdweE9uZgpOT3VkOElYWUs3M3pTY3VFOEUxNTdZd3B6Q0twVmFIWDdaSmY4UXVOc09PNVcvYUlqS2wzTDYyNjkrZUlPRXJHCndPTm1hSG56Zmc5RkxwSmh6Z3BPMzhhVm43NnZENUtLakJhaldza1krNGEyZ1NRbUtOZUZxYXFPb3p5RUZNMGUKY0ZXWlZWWjNMZWg0dkVNb1lWUHlJeng5Nng4ZjIveW1QbmhJdXZRdjV3TjRmeWVwYTdFWTVVQ2NwNzF6OGtmUAo0RmNVelBBMElEV3lNaWhYUi9HNlhnUVFaNEdiL3FCQmh2cnZpSkNGemZZRGNKZ0w3RmVnRllIUDNQR0wwN1FnCnZMZXZNSytpUVpQcnhyYnh5U3FkUE9rZ3VyS2pWclhUVXI0QTlUZ2lMeUlYNVVsSnEzRS9SVjdtZk9xWm5MVGEKU0NWWEhCaHVQbG5DR1pSMDFUb1RDZktoTUcxdTBDRm5MMisxNWhDOWZxT21XdjlRa2U0M3FsSjBQZ0YzVkovWAp1eC9tVHBuazlnbmJHOUpIK21mSDM5Um9GdlROaW5Zd1NNdll6dXRWT242OXNPemR3aERsYTkwbDNBQ2g0eENWCks3Sk9YK3VIa29OdTNnMmlWeGlaVU0wQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=';
export default function decrypt(encrypted) {
const decrypted = crypto.publicDecrypt(Buffer.from(publicKey, 'base64'), Buffer.from(encrypted, 'base64'));
export default function decrypt(encrypted: string): string {
const decrypted = crypto.publicDecrypt(Buffer.from(publicKey, 'base64').toString('utf-8'), Buffer.from(encrypted, 'base64'));
return decrypted.toString('utf-8');
}

@ -1,6 +0,0 @@
import { onLicense } from './license';
import './settings';
import './methods';
import './startup';
export { onLicense };

@ -0,0 +1,5 @@
import './settings';
import './methods';
import './startup';
export { onLicense, overwriteClassOnLicense } from './license';

@ -1,30 +1,48 @@
import EventEmiter from 'events';
import EventEmitter from 'events';
import { Users } from '../../../../app/models/server';
import decrypt from './decrypt';
import { getBundleModules, isBundle } from './bundles';
const EnterpriseLicenses = new EventEmiter();
const EnterpriseLicenses = new EventEmitter();
const License = {
url: null,
licenses: [],
modules: {},
export interface IModules {
[key: string]: number;
}
export interface ILicense {
url: string;
expiry: string;
maxActiveUsers: number;
modules: string[];
}
export interface IValidLicense {
valid?: boolean;
license: ILicense;
}
class LicenseClass {
private url: string|null = null;
private licenses: IValidLicense[] = [];
_validateExpiration(expiration) {
private modules: IModules = {};
_validateExpiration(expiration: string): boolean {
return new Date() > new Date(expiration);
},
}
_validateURL(licenseURL, url) {
_validateURL(licenseURL: string, url: string): boolean {
licenseURL = licenseURL
.replace(/\./g, '\\.') // convert dots to literal
.replace(/\*/g, '.*'); // convert * to .*
const regex = new RegExp(`^${ licenseURL }$`, 'i');
return regex.exec(url);
},
return !!regex.exec(url);
}
_validModules(licenseModules) {
_validModules(licenseModules: string[]): void {
licenseModules.forEach((licenseModule) => {
const modules = isBundle(licenseModule)
? getBundleModules(licenseModule)
@ -35,9 +53,9 @@ const License = {
EnterpriseLicenses.emit(`valid:${ module }`);
});
});
},
}
_invalidModules(licenseModules) {
_invalidModules(licenseModules: string[]): void {
licenseModules.forEach((licenseModule) => {
const modules = isBundle(licenseModule)
? getBundleModules(licenseModule)
@ -45,32 +63,32 @@ const License = {
modules.forEach((module) => EnterpriseLicenses.emit(`invalid:${ module }`));
});
},
}
_hasValidNumberOfActiveUsers(maxActiveUsers) {
_hasValidNumberOfActiveUsers(maxActiveUsers: number): boolean {
return Users.getActiveLocalUserCount() <= maxActiveUsers;
},
}
addLicense(license) {
addLicense(license: ILicense): void {
this.licenses.push({
valid: null,
valid: undefined,
license,
});
this.validate();
},
}
hasModule(module) {
hasModule(module: string): boolean {
return typeof this.modules[module] !== 'undefined';
},
}
setURL(url) {
setURL(url: string): void {
this.url = url.replace(/\/$/, '').replace(/^https?:\/\/(.*)$/, '$1');
this.validate();
},
}
validate() {
validate(): void {
this.licenses = this.licenses.map((item) => {
const { license } = item;
@ -79,20 +97,20 @@ const License = {
return item;
}
if (!this._validateURL(license.url, this.url)) {
license.valid = false;
item.valid = false;
this._invalidModules(license.modules);
return item;
}
}
if (license.expiry && this._validateExpiration(license.expiry)) {
license.valid = false;
item.valid = false;
this._invalidModules(license.modules);
return item;
}
if (license.maxActiveUsers && !this._hasValidNumberOfActiveUsers(parseInt(license.maxActiveUsers))) {
license.valid = false;
if (license.maxActiveUsers && !this._hasValidNumberOfActiveUsers(license.maxActiveUsers)) {
item.valid = false;
this._invalidModules(license.modules);
return item;
}
@ -106,9 +124,9 @@ const License = {
});
this.showLicenses();
},
}
showLicenses() {
showLicenses(): void {
if (!process.env.LICENSE_DEBUG || process.env.LICENSE_DEBUG === 'false') {
return;
}
@ -125,20 +143,22 @@ const License = {
console.log(' modules ->', license.modules.join(', '));
console.log('-------------------------');
});
},
};
}
}
const License = new LicenseClass();
export function addLicense(encryptedLicense) {
export function addLicense(encryptedLicense: string): boolean {
if (!encryptedLicense || String(encryptedLicense).trim() === '') {
return;
return false;
}
console.log('### New Enteprise License');
console.log('### New Enterprise License');
try {
const decrypted = decrypt(encryptedLicense);
if (!decrypted) {
return;
return false;
}
if (process.env.LICENSE_DEBUG && process.env.LICENSE_DEBUG !== 'false') {
@ -153,21 +173,39 @@ export function addLicense(encryptedLicense) {
if (process.env.LICENSE_DEBUG && process.env.LICENSE_DEBUG !== 'false') {
console.error('##### Invalid raw license ->', encryptedLicense, e);
}
return false;
}
}
export function setURL(url) {
export function setURL(url: string): void {
License.setURL(url);
}
export function hasLicense(feature) {
export function hasLicense(feature: string): boolean {
return License.hasModule(feature);
}
export function onLicense(feature, cb) {
export function onLicense(feature: string, cb: (...args: any[]) => void): void {
if (hasLicense(feature)) {
return cb();
}
EnterpriseLicenses.once(`valid:${ feature }`, cb);
}
export interface IOverrideClassProperties {
[key: string]: (...args: any[]) => any;
}
type Class = { new(...args: any[]): any };
export function overwriteClassOnLicense(license: string, original: Class, overwrite: IOverrideClassProperties): void {
onLicense(license, () => {
Object.entries(overwrite).forEach(([key, value]) => {
const originalFn = original.prototype[key];
original.prototype[key] = function(...args: any[]): any {
return value.call(this, originalFn, ...args);
};
});
});
}

@ -1,9 +1,12 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { hasLicense } from './license';
Meteor.methods({
'license:hasLicense'(feature) {
'license:hasLicense'(feature: string) {
check(feature, String);
return hasLicense(feature);
},
});

@ -11,5 +11,6 @@ export function getUnitsFromUser() {
return;
}
// TODO remove this Meteor.call as this is used undirectly by models
return Meteor.call('livechat:getUnitsFromUserRoles');
}

@ -1,64 +1,41 @@
import { LivechatDepartment } from '../../../../../app/models/server/models/LivechatDepartment';
import { logger } from '../../../livechat-enterprise/server/lib/logger';
import { addQueryRestrictionsToDepartmentsModel } from '../../../livechat-enterprise/server/lib/query.helper';
import { onLicense } from '../../../license/server';
import { overwriteClassOnLicense } from '../../../license/server';
const _find = LivechatDepartment.prototype.find;
const _findOne = LivechatDepartment.prototype.findOne;
const _update = LivechatDepartment.prototype.update;
const _remove = LivechatDepartment.prototype.remove;
const _createOrUpdateDepartment = LivechatDepartment.prototype.createOrUpdateDepartment;
const { find, findOne, update, remove } = LivechatDepartment.prototype;
LivechatDepartment.prototype.unfilteredFind = function(...args) {
return _find.call(this, ...args);
const applyRestrictions = (method) => function(originalFn, originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug(() => `LivechatDepartment.${ method } - ${ JSON.stringify(query) }`);
return originalFn.call(this, query, ...args);
};
LivechatDepartment.prototype.unfilteredFindOne = function(...args) {
return _findOne.call(this, ...args);
};
LivechatDepartment.prototype.unfilteredUpdate = function(...args) {
return _update.call(this, ...args);
};
LivechatDepartment.prototype.unfilteredRemove = function(...args) {
return _remove.call(this, ...args);
};
onLicense('livechat-enterprise', () => {
LivechatDepartment.prototype.find = function(originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug('LivechatDepartment.find', JSON.stringify(query));
return _find.call(this, query, ...args);
};
LivechatDepartment.prototype.findOne = function(originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug('LivechatDepartment.findOne', JSON.stringify(query));
return _findOne.call(this, query, ...args);
};
LivechatDepartment.prototype.update = function(originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug('LivechatDepartment.update', JSON.stringify(query));
return _update.call(this, query, ...args);
};
LivechatDepartment.prototype.remove = function(originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug('LivechatDepartment.remove', JSON.stringify(query));
return _remove.call(this, query, ...args);
};
LivechatDepartment.prototype.createOrUpdateDepartment = function(...args) {
overwriteClassOnLicense('livechat-enterprise', LivechatDepartment, {
find: applyRestrictions('find'),
findOne: applyRestrictions('findOne'),
update: applyRestrictions('update'),
remove: applyRestrictions('remove'),
unfilteredFind(originalFn, ...args) {
return find.apply(this, args);
},
unfilteredFindOne(originalFn, ...args) {
return findOne.apply(this, args);
},
unfilteredUpdate(originalFn, ...args) {
return update.apply(this, args);
},
unfilteredRemove(originalFn, ...args) {
return remove.apply(this, args);
},
createOrUpdateDepartment(originalFn, ...args) {
if (args.length > 2 && !args[1].type) {
args[1].type = 'd';
}
return _createOrUpdateDepartment.apply(this, args);
};
LivechatDepartment.prototype.removeParentAndAncestorById = function(parentId) {
return originalFn.apply(this, args);
},
removeParentAndAncestorById(originalFn, parentId) {
const query = {
parentId,
};
@ -69,7 +46,7 @@ onLicense('livechat-enterprise', () => {
};
return this.update(query, update, { multi: true });
};
},
});
export default LivechatDepartment;

@ -1,42 +1,26 @@
import { LivechatRooms } from '../../../../../app/models/server/models/LivechatRooms';
import { logger } from '../../../livechat-enterprise/server/lib/logger';
import { addQueryRestrictionsToRoomsModel } from '../../../livechat-enterprise/server/lib/query.helper';
import { overwriteClassOnLicense } from '../../../license/server';
const _find = LivechatRooms.prototype.find;
const _findOne = LivechatRooms.prototype.findOne;
const _update = LivechatRooms.prototype.update;
const _remove = LivechatRooms.prototype.remove;
LivechatRooms.prototype.find = function(originalQuery, ...args) {
const query = addQueryRestrictionsToRoomsModel(originalQuery);
logger.queries.debug('LivechatRooms.find', JSON.stringify(query));
return _find.call(this, query, ...args);
};
LivechatRooms.prototype.findOne = function(originalQuery, ...args) {
const applyRestrictions = (method) => function(originalFn, originalQuery, ...args) {
const query = addQueryRestrictionsToRoomsModel(originalQuery);
logger.queries.debug('LivechatRooms.findOne', JSON.stringify(query));
return _findOne.call(this, query, ...args);
logger.queries.debug(() => `LivechatRooms.${ method } - ${ JSON.stringify(query) }`);
return originalFn.call(this, query, ...args);
};
LivechatRooms.prototype.update = function(originalQuery, ...args) {
const query = addQueryRestrictionsToRoomsModel(originalQuery);
logger.queries.debug('LivechatRooms.update', JSON.stringify(query));
return _update.call(this, query, ...args);
};
LivechatRooms.prototype.remove = function(originalQuery, ...args) {
const query = addQueryRestrictionsToRoomsModel(originalQuery);
logger.queries.debug('LivechatRooms.remove', JSON.stringify(query));
return _remove.call(this, query, ...args);
};
LivechatRooms.prototype.updateDepartmentAncestorsById = function(_id, departmentAncestors) {
const query = {
_id,
};
const update = departmentAncestors ? { $set: { departmentAncestors } } : { $unset: { departmentAncestors: 1 } };
return this.update(query, update);
};
overwriteClassOnLicense('livechat-enterprise', LivechatRooms, {
find: applyRestrictions('find'),
findOne: applyRestrictions('findOne'),
update: applyRestrictions('update'),
remove: applyRestrictions('remove'),
updateDepartmentAncestorsById(originalFn, _id, departmentAncestors) {
const query = {
_id,
};
const update = departmentAncestors ? { $set: { departmentAncestors } } : { $unset: { departmentAncestors: 1 } };
return this.update(query, update);
},
});
export default LivechatRooms;

@ -1,32 +1,17 @@
import { LivechatDepartmentRaw } from '../../../../../app/models/server/raw/LivechatDepartment';
import { logger } from '../../../livechat-enterprise/server/lib/logger';
import { addQueryRestrictionsToDepartmentsModel } from '../../../livechat-enterprise/server/lib/query.helper';
import { overwriteClassOnLicense } from '../../../license/server';
const _find = LivechatDepartmentRaw.prototype.find;
const _findOne = LivechatDepartmentRaw.prototype.findOne;
const _update = LivechatDepartmentRaw.prototype.update;
const _remove = LivechatDepartmentRaw.prototype.remove;
LivechatDepartmentRaw.prototype.find = function(originalQuery, ...args) {
const applyRestrictions = (method) => function(originalFn, originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug('LivechatDepartmentRaw.find', JSON.stringify(query));
return _find.call(this, query, ...args);
logger.queries.debug(() => `LivechatDepartmentRaw.${ method } - ${ JSON.stringify(query) }`);
return originalFn.call(this, query, ...args);
};
LivechatDepartmentRaw.prototype.findOne = function(originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug('LivechatDepartmentRaw.findOne', JSON.stringify(query));
return _findOne.call(this, query, ...args);
};
LivechatDepartmentRaw.prototype.update = function(originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug('LivechatDepartmentRaw.update', JSON.stringify(query));
return _update.call(this, query, ...args);
};
LivechatDepartmentRaw.prototype.remove = function(originalQuery, ...args) {
const query = addQueryRestrictionsToDepartmentsModel(originalQuery);
logger.queries.debug('LivechatDepartmentRaw.remove', JSON.stringify(query));
return _remove.call(this, query, ...args);
};
overwriteClassOnLicense('livechat-enterprise', LivechatDepartmentRaw, {
find: applyRestrictions('find'),
findOne: applyRestrictions('findOne'),
update: applyRestrictions('update'),
remove: applyRestrictions('remove'),
});

@ -1,32 +1,24 @@
import { LivechatRoomsRaw } from '../../../../../app/models/server/raw/LivechatRooms';
import { logger } from '../../../livechat-enterprise/server/lib/logger';
import { addQueryRestrictionsToRoomsModel } from '../../../livechat-enterprise/server/lib/query.helper';
import { overwriteClassOnLicense } from '../../../license/server';
const _find = LivechatRoomsRaw.prototype.find;
const _findOne = LivechatRoomsRaw.prototype.findOne;
const _update = LivechatRoomsRaw.prototype.update;
const _remove = LivechatRoomsRaw.prototype.remove;
LivechatRoomsRaw.prototype.find = function(originalQuery, ...args) {
const applyRestrictions = (method) => function(originalFn, originalQuery, ...args) {
const query = addQueryRestrictionsToRoomsModel(originalQuery);
logger.queries.debug('LivechatRoomsRaw.find', JSON.stringify(query));
return _find.call(this, query, ...args);
logger.queries.debug(() => `LivechatRoomsRaw.${ method } - ${ JSON.stringify(query) }`);
return originalFn.call(this, query, ...args);
};
LivechatRoomsRaw.prototype.findOne = function(originalQuery, ...args) {
const query = addQueryRestrictionsToRoomsModel(originalQuery);
logger.queries.debug('LivechatRoomsRaw.findOne', JSON.stringify(query));
return _findOne.call(this, query, ...args);
};
LivechatRoomsRaw.prototype.update = function(originalQuery, ...args) {
const query = addQueryRestrictionsToRoomsModel(originalQuery);
logger.queries.debug('LivechatRoomsRaw.update', JSON.stringify(query));
return _update.call(this, query, ...args);
};
LivechatRoomsRaw.prototype.remove = function(originalQuery, ...args) {
const query = addQueryRestrictionsToRoomsModel(originalQuery);
logger.queries.debug('LivechatRoomsRaw.remove', JSON.stringify(query));
return _remove.call(this, query, ...args);
};
overwriteClassOnLicense('livechat-enterprise', LivechatRoomsRaw, {
find: applyRestrictions('find'),
findOne: applyRestrictions('findOne'),
update: applyRestrictions('update'),
remove: applyRestrictions('remove'),
updateDepartmentAncestorsById(originalFn, _id, departmentAncestors) {
const query = {
_id,
};
const update = departmentAncestors ? { $set: { departmentAncestors } } : { $unset: { departmentAncestors: 1 } };
return this.update(query, update);
},
});

Loading…
Cancel
Save