Skip to content

Commit

Permalink
avoid triggering "invalidate" events when an active license is replac…
Browse files Browse the repository at this point in the history
…ed with another valid license.
  • Loading branch information
pierre-lehnen-rc authored and ggazzo committed Sep 22, 2023
1 parent 25c1491 commit b5e1a43
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 39 deletions.
8 changes: 8 additions & 0 deletions ee/packages/license/src/actionBlockers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ export const preventNewPrivateApps = async (appCount = 1) => shouldPreventAction
export const preventNewMarketplaceApps = async (appCount = 1) => shouldPreventAction('marketplaceApps', {}, appCount);
export const preventNewGuestSubscriptions = async (guest: IUser['_id'], roomCount = 1) =>
shouldPreventAction('roomsPerGuest', { userId: guest }, roomCount);
export const preventNewActiveContacts = async (contactCount = 1) => shouldPreventAction('monthlyActiveContacts', {}, contactCount);

export const userLimitReached = async () => preventNewUsers(0);
export const guestLimitReached = async () => preventNewGuests(0);
export const privateAppLimitReached = async () => preventNewPrivateApps(0);
export const marketplaceAppLimitReached = async () => preventNewMarketplaceApps(0);
export const guestSubscriptionLimitReached = async (guest: IUser['_id']) => preventNewGuestSubscriptions(guest, 0);
export const macLimitReached = async () => preventNewActiveContacts(0);
33 changes: 27 additions & 6 deletions ee/packages/license/src/events/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import { EventEmitter } from 'events';

import type { LicenseModule } from '../definition/LicenseModule';
import { logger } from '../logger';

export const EnterpriseLicenses = new EventEmitter();

export const licenseValidated = () => EnterpriseLicenses.emit('validate');
export const licenseValidated = () => {
try {
EnterpriseLicenses.emit('validate');
} catch (error) {
logger.error({ msg: 'Error running license validated event', error });
}
};

export const licenseRemoved = () => EnterpriseLicenses.emit('invalidate');
export const licenseRemoved = () => {
try {
EnterpriseLicenses.emit('invalidate');
} catch (error) {
logger.error({ msg: 'Error running license invalidated event', error });
}
};

export const moduleValidated = (module: LicenseModule) => {
EnterpriseLicenses.emit('module', { module, valid: true });
EnterpriseLicenses.emit(`valid:${module}`);
try {
EnterpriseLicenses.emit('module', { module, valid: true });
EnterpriseLicenses.emit(`valid:${module}`);
} catch (error) {
logger.error({ msg: 'Error running module added event', error });
}
};

export const moduleRemoved = (module: LicenseModule) => {
EnterpriseLicenses.emit('module', { module, valid: false });
EnterpriseLicenses.emit(`invalid:${module}`);
try {
EnterpriseLicenses.emit('module', { module, valid: false });
EnterpriseLicenses.emit(`invalid:${module}`);
} catch (error) {
logger.error({ msg: 'Error running module removed event', error });
}
};
81 changes: 54 additions & 27 deletions ee/packages/license/src/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,28 @@ import type { BehaviorWithContext } from './definition/LicenseBehavior';
import { isLicenseDuplicate, lockLicense } from './encryptedLicense';
import { licenseRemoved, licenseValidated } from './events/emitter';
import { logger } from './logger';
import { getModules, invalidateAll, notifyValidatedModules } from './modules';
import { getModules, invalidateAll, replaceModules } from './modules';
import { clearPendingLicense, hasPendingLicense, isPendingLicense, setPendingLicense } from './pendingLicense';
import { showLicense } from './showLicense';
import { addTags } from './tags';
import { replaceTags } from './tags';
import { convertToV3 } from './v2/convertToV3';
import { getModulesToDisable } from './validation/getModulesToDisable';
import { isBehaviorsInResult } from './validation/isBehaviorsInResult';
import { runValidation } from './validation/runValidation';
import { validateFormat } from './validation/validateFormat';
import { getWorkspaceUrl } from './workspaceUrl';

let unmodifiedLicense: ILicenseV2 | ILicenseV3 | undefined;
let license: ILicenseV3 | undefined;
let valid: boolean | undefined;
let inFairPolicy: boolean | undefined;

const removeCurrentLicense = () => {
const oldLicense = license;
const wasValid = valid;

const clearLicenseData = () => {
license = undefined;
unmodifiedLicense = undefined;
valid = undefined;
inFairPolicy = undefined;

if (!oldLicense || !wasValid) {
return;
}

valid = false;

licenseRemoved();
invalidateAll();
};

const processValidationResult = (result: BehaviorWithContext[]) => {
Expand All @@ -46,21 +38,21 @@ const processValidationResult = (result: BehaviorWithContext[]) => {
inFairPolicy = isBehaviorsInResult(result, ['start_fair_policy']);

if (license.information.tags) {
addTags(license.information.tags);
replaceTags(license.information.tags);
}

const disabledModules = getModulesToDisable(result);
const modulesToEnable = license.grantedModules.filter(({ module }) => !disabledModules.includes(module));

notifyValidatedModules(modulesToEnable.map(({ module }) => module));
replaceModules(modulesToEnable.map(({ module }) => module));
logger.log({ msg: 'License validated', modules: modulesToEnable });

licenseValidated();
showLicense(license, valid);
};

export const validateLicense = async () => {
if (!license) {
if (!license || !getWorkspaceUrl()) {
return;
}

Expand All @@ -74,18 +66,54 @@ export const validateLicense = async () => {
processValidationResult(validationResult);
};

const setLicenseV3 = async (newLicense: ILicenseV3, originalLicense?: ILicenseV2 | ILicenseV3) => {
removeCurrentLicense();
unmodifiedLicense = originalLicense || newLicense;
license = newLicense;
const setLicenseV3 = async (newLicense: ILicenseV3, encryptedLicense: string, originalLicense?: ILicenseV2 | ILicenseV3) => {
const hadValidLicense = isEnterprise();
clearLicenseData();

await validateLicense();
try {
unmodifiedLicense = originalLicense || newLicense;
license = newLicense;
clearPendingLicense();

await validateLicense();
lockLicense(encryptedLicense);
} finally {
if (hadValidLicense && !isEnterprise()) {
licenseRemoved();
invalidateAll();
}
}
};

const setLicenseV2 = async (newLicense: ILicenseV2) => setLicenseV3(convertToV3(newLicense), newLicense);
const setLicenseV2 = async (newLicense: ILicenseV2, encryptedLicense: string) =>
setLicenseV3(convertToV3(newLicense), encryptedLicense, newLicense);

// Can only validate licenses once the workspace URL is set
export const isReadyForValidation = () => Boolean(getWorkspaceUrl());

export const setLicense = async (encryptedLicense: string, forceSet = false): Promise<boolean> => {
if (!encryptedLicense || String(encryptedLicense).trim() === '') {
return false;
}

if (isLicenseDuplicate(encryptedLicense)) {
// If there is a pending license but the user is trying to revert to the license that is currently active
if (hasPendingLicense() && !isPendingLicense(encryptedLicense)) {
// simply remove the pending license
clearPendingLicense();
return true;
}

return false;
}

if (!isReadyForValidation() && !forceSet) {
// If we can't validate the license data yet, but is a valid license string, store it to validate when we can
if (validateFormat(encryptedLicense)) {
setPendingLicense(encryptedLicense);
return true;
}

export const setLicense = async (encryptedLicense: string): Promise<boolean> => {
if (!encryptedLicense || String(encryptedLicense).trim() === '' || isLicenseDuplicate(encryptedLicense)) {
return false;
}

Expand All @@ -101,8 +129,7 @@ export const setLicense = async (encryptedLicense: string): Promise<boolean> =>
}

// #TODO: Check license version and call setLicenseV2 or setLicenseV3
await setLicenseV2(JSON.parse(decrypted));
lockLicense(encryptedLicense);
await setLicenseV2(JSON.parse(decrypted), encryptedLicense);

return true;
} catch (e) {
Expand Down
20 changes: 20 additions & 0 deletions ee/packages/license/src/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,23 @@ export const invalidateAll = () => {
export const getModules = () => [...modules];

export const hasModule = (module: LicenseModule) => modules.has(module);

export const replaceModules = (newModules: LicenseModule[]) => {
for (const moduleName of newModules) {
if (modules.has(moduleName)) {
continue;
}

modules.add(moduleName);
moduleValidated(moduleName);
}

for (const moduleName of modules) {
if (newModules.includes(moduleName)) {
continue;
}

moduleRemoved(moduleName);
modules.delete(moduleName);
}
};
30 changes: 30 additions & 0 deletions ee/packages/license/src/pendingLicense.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { setLicense } from './license';
import { logger } from './logger';

let pendingLicense: string;

export const setPendingLicense = (encryptedLicense: string) => {
pendingLicense = encryptedLicense;
if (pendingLicense) {
logger.info('Storing license as pending validation.');
}
};

export const applyPendingLicense = async () => {
if (pendingLicense) {
logger.info('Applying pending license.');
await setLicense(pendingLicense, true);
}
};

export const hasPendingLicense = () => Boolean(pendingLicense);

export const isPendingLicense = (encryptedLicense: string) => !!pendingLicense && pendingLicense === encryptedLicense;

export const clearPendingLicense = () => {
if (pendingLicense) {
logger.info('Removing pending license.');
}

pendingLicense = '';
};
5 changes: 3 additions & 2 deletions ee/packages/license/src/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ export const addTag = (tag: ILicenseTag) => {
tags.add(tag);
};

export const addTags = (tags: ILicenseTag[]) => {
for (const tag of tags) {
export const replaceTags = (newTags: ILicenseTag[]) => {
tags.clear();
for (const tag of newTags) {
addTag(tag);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { IUser } from '@rocket.chat/core-typings';
import { Subscriptions, Users } from '@rocket.chat/models';

import type { LicenseLimitKind } from '../definition/ILicenseV3';
import { logger } from '../logger';

type LimitContext<T extends LicenseLimitKind> = T extends 'roomsPerGuest' ? { userId: IUser['_id'] } : Record<string, never>;

Expand All @@ -23,5 +24,7 @@ export const getCurrentValueForLicenseLimit = async <T extends LicenseLimitKind>
return dataCounters.get(limitKey)?.(context as LimitContext<LicenseLimitKind> | undefined) ?? 0;
}

logger.error({ msg: 'Unable to validate license limit due to missing data counter.', limitKey });

return 0;
};
2 changes: 1 addition & 1 deletion ee/packages/license/src/validation/getModulesToDisable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const getModulesToDisable = (validationResult: BehaviorWithContext[]): Li
...new Set([
...filterValidationResult(validationResult, 'disable_modules')
.map(({ modules }) => modules || [])
.reduce((prev, curr) => [...prev, ...curr], []),
.flat(),
]),
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ export const validateLicenseLimits = async (
});
}),
)
).reduce((prev, curr) => [...prev, ...curr], []);
).flat();
};
6 changes: 4 additions & 2 deletions ee/packages/license/src/workspaceUrl.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { validateLicense } from './license';
import { applyPendingLicense, hasPendingLicense } from './pendingLicense';

let workspaceUrl: string | undefined;

export const setWorkspaceUrl = async (url: string) => {
workspaceUrl = url.replace(/\/$/, '').replace(/^https?:\/\/(.*)$/, '$1');

await validateLicense();
if (hasPendingLicense()) {
await applyPendingLicense();
}
};

export const getWorkspaceUrl = () => workspaceUrl;

0 comments on commit b5e1a43

Please sign in to comment.