chore: License v3 - create default restrictions if there is no license (#30808)

pull/30637/head^2
Guilherme Gazzo 2 years ago committed by GitHub
parent d20c79aa31
commit 2f40971a65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      apps/meteor/ee/client/views/admin/users/useSeatsCap.ts
  2. 4
      apps/meteor/ee/server/api/licenses.ts
  3. 49
      ee/packages/license/__tests__/DefaultRestrictions.spec.ts
  4. 14
      ee/packages/license/src/deprecated.ts
  5. 39
      ee/packages/license/src/license.ts
  6. 28
      ee/packages/license/src/validation/validateDefaultLimits.ts
  7. 51
      ee/packages/license/src/validation/validateLicenseLimits.ts
  8. 28
      ee/packages/license/src/validation/validateLimit.ts
  9. 44
      ee/packages/license/src/validation/validateLimits.ts

@ -19,7 +19,7 @@ export const useSeatsCap = (): SeatCapProps | undefined => {
return {
activeUsers: result.data.activeUsers,
maxActiveUsers: result.data.maxActiveUsers ?? Number.POSITIVE_INFINITY,
maxActiveUsers: result.data.maxActiveUsers && result.data.maxActiveUsers > 0 ? result.data.maxActiveUsers : Number.POSITIVE_INFINITY,
reload: () => result.refetch(),
};
};

@ -71,10 +71,10 @@ API.v1.addRoute(
{ authRequired: true },
{
async get() {
const maxActiveUsers = License.getMaxActiveUsers() || null;
const maxActiveUsers = License.getMaxActiveUsers();
const activeUsers = await Users.getActiveLocalUserCount();
return API.v1.success({ maxActiveUsers, activeUsers });
return API.v1.success({ maxActiveUsers: maxActiveUsers > 0 ? maxActiveUsers : null, activeUsers });
},
},
);

@ -0,0 +1,49 @@
import { LicenseImp } from '../src';
describe('Community Restrictions', () => {
describe('Apps from marketplace', () => {
it('should respect the default if there is no license applied', async () => {
const license = new LicenseImp();
license.setLicenseLimitCounter('marketplaceApps', () => 1);
await expect(await license.shouldPreventAction('marketplaceApps')).toBe(false);
license.setLicenseLimitCounter('marketplaceApps', () => 10);
await expect(await license.shouldPreventAction('marketplaceApps')).toBe(true);
});
});
describe('Private Apps', () => {
it('should respect the default if there is no license applied', async () => {
const license = new LicenseImp();
license.setLicenseLimitCounter('privateApps', () => 1);
await expect(await license.shouldPreventAction('privateApps')).toBe(false);
license.setLicenseLimitCounter('privateApps', () => 10);
await expect(await license.shouldPreventAction('privateApps')).toBe(true);
});
});
describe('Active Users', () => {
it('should respect the default if there is no license applied', async () => {
const license = new LicenseImp();
license.setLicenseLimitCounter('activeUsers', () => 1);
await expect(await license.shouldPreventAction('activeUsers')).toBe(false);
license.setLicenseLimitCounter('activeUsers', () => 10);
await expect(await license.shouldPreventAction('activeUsers')).toBe(false);
license.setLicenseLimitCounter('activeUsers', () => 100000);
await expect(await license.shouldPreventAction('activeUsers')).toBe(false);
});
});
});

@ -1,15 +1,13 @@
import type { ILicenseV3, LicenseLimitKind } from './definition/ILicenseV3';
import type { LicenseManager } from './license';
import { getModules } from './modules';
import { defaultLimits } from './validation/validateDefaultLimits';
const getLicenseLimit = (license: ILicenseV3 | undefined, kind: LicenseLimitKind) => {
if (!license) {
return;
}
export const getLicenseLimit = (license: ILicenseV3 | undefined, kind: LicenseLimitKind) => {
const limitList = license?.limits[kind] ?? defaultLimits[kind as keyof typeof defaultLimits];
const limitList = license.limits[kind];
if (!limitList?.length) {
return;
return -1;
}
return Math.min(...limitList.map(({ max }) => max));
@ -23,8 +21,8 @@ export function getMaxActiveUsers(this: LicenseManager) {
export function getAppsConfig(this: LicenseManager) {
return {
maxPrivateApps: getLicenseLimit(this.getLicense(), 'privateApps') ?? 3,
maxMarketplaceApps: getLicenseLimit(this.getLicense(), 'marketplaceApps') ?? 5,
maxPrivateApps: getLicenseLimit(this.getLicense(), 'privateApps'),
maxMarketplaceApps: getLicenseLimit(this.getLicense(), 'marketplaceApps'),
};
}

@ -9,6 +9,7 @@ import type { LicenseModule } from './definition/LicenseModule';
import type { LicenseValidationOptions } from './definition/LicenseValidationOptions';
import type { LimitContext } from './definition/LimitContext';
import type { LicenseEvents } from './definition/events';
import { getLicenseLimit } from './deprecated';
import { DuplicatedLicenseError } from './errors/DuplicatedLicenseError';
import { InvalidLicenseError } from './errors/InvalidLicenseError';
import { NotReadyForValidation } from './errors/NotReadyForValidation';
@ -26,6 +27,7 @@ import { getModulesToDisable } from './validation/getModulesToDisable';
import { isBehaviorsInResult } from './validation/isBehaviorsInResult';
import { isReadyForValidation } from './validation/isReadyForValidation';
import { runValidation } from './validation/runValidation';
import { validateDefaultLimits } from './validation/validateDefaultLimits';
import { validateFormat } from './validation/validateFormat';
import { validateLicenseLimits } from './validation/validateLicenseLimits';
@ -349,11 +351,6 @@ export class LicenseManager extends Emitter<LicenseEvents> {
context: Partial<LimitContext<T>> = {},
{ suppressLog }: Pick<LicenseValidationOptions, 'suppressLog'> = {},
): Promise<boolean> {
const license = this.getLicense();
if (!license) {
return false;
}
const options: LicenseValidationOptions = {
...(extraCount && { behaviors: ['prevent_action'] }),
isNewLicense: false,
@ -367,6 +364,11 @@ export class LicenseManager extends Emitter<LicenseEvents> {
},
};
const license = this.getLicense();
if (!license) {
return isBehaviorsInResult(await validateDefaultLimits.call(this, options), ['prevent_action']);
}
const validationResult = await runValidation.call(this, license, options);
const shouldPreventAction = isBehaviorsInResult(validationResult, ['prevent_action']);
@ -415,27 +417,24 @@ export class LicenseManager extends Emitter<LicenseEvents> {
const license = this.getLicense();
// Get all limits present in the license and their current value
const limits = (
(license &&
includeLimits &&
const limits = Object.fromEntries(
(includeLimits &&
(await Promise.all(
globalLimitKinds
.map((limitKey) => ({
limitKey,
max: Math.max(-1, Math.min(...Array.from(license.limits[limitKey as LicenseLimitKind] || [])?.map(({ max }) => max))),
}))
.filter(({ max }) => max >= 0 && max < Infinity)
.map(async ({ max, limitKey }) => {
return {
[limitKey as LicenseLimitKind]: {
...(loadCurrentValues ? { value: await getCurrentValueForLicenseLimit.call(this, limitKey as LicenseLimitKind) } : {}),
.map((limitKey) => [limitKey, getLicenseLimit(license, limitKey)] as const)
.filter(([, max]) => max >= 0 && max < Infinity)
.map(async ([limitKey, max]) => {
return [
limitKey,
{
...(loadCurrentValues && { value: await getCurrentValueForLicenseLimit.call(this, limitKey) }),
max,
},
};
];
}),
))) ||
[]
).reduce((prev, curr) => ({ ...prev, ...curr }), {});
[],
);
return {
license: (includeLicense && license) || undefined,

@ -0,0 +1,28 @@
import type { BehaviorWithContext } from '../definition/LicenseBehavior';
import type { LicenseLimit } from '../definition/LicenseLimit';
import type { LicenseValidationOptions } from '../definition/LicenseValidationOptions';
import type { LicenseManager } from '../license';
import { validateLimits } from './validateLimits';
export const defaultLimits: {
privateApps: LicenseLimit[];
marketplaceApps: LicenseLimit[];
// monthlyActiveContacts?: LicenseLimit[];
} = {
privateApps: [
{
behavior: 'prevent_action',
max: 3,
},
],
marketplaceApps: [
{
behavior: 'prevent_action',
max: 5,
},
],
};
export async function validateDefaultLimits(this: LicenseManager, options: LicenseValidationOptions): Promise<BehaviorWithContext[]> {
return validateLimits.call(this, defaultLimits, options);
}

@ -1,11 +1,8 @@
import type { ILicenseV3, LicenseLimitKind } from '../definition/ILicenseV3';
import type { ILicenseV3 } from '../definition/ILicenseV3';
import type { BehaviorWithContext } from '../definition/LicenseBehavior';
import type { LicenseValidationOptions } from '../definition/LicenseValidationOptions';
import { isLimitAllowed, isBehaviorAllowed } from '../isItemAllowed';
import type { LicenseManager } from '../license';
import { logger } from '../logger';
import { getCurrentValueForLicenseLimit } from './getCurrentValueForLicenseLimit';
import { getResultingBehavior } from './getResultingBehavior';
import { validateLimits } from './validateLimits';
export async function validateLicenseLimits(
this: LicenseManager,
@ -14,47 +11,5 @@ export async function validateLicenseLimits(
): Promise<BehaviorWithContext[]> {
const { limits } = license;
const limitKeys = (Object.keys(limits) as LicenseLimitKind[]).filter((limit) => isLimitAllowed(limit, options));
return (
await Promise.all(
limitKeys.map(async (limitKey) => {
// Filter the limit list before running any query in the database so we don't end up loading some value we won't use.
const limitList = limits[limitKey]?.filter(({ behavior, max }) => max >= 0 && isBehaviorAllowed(behavior, options));
if (!limitList?.length) {
return [];
}
const extraCount = options.context?.[limitKey]?.extraCount ?? 0;
const currentValue = (await getCurrentValueForLicenseLimit.call(this, limitKey, options.context?.[limitKey])) + extraCount;
return limitList
.filter(({ max, behavior }) => {
switch (behavior) {
case 'invalidate_license':
case 'prevent_installation':
case 'disable_modules':
case 'start_fair_policy':
default:
return currentValue > max;
case 'prevent_action':
/**
* if we are validating the current count the limit should be equal or over the max, if we are validating the future count the limit should be over the max
*/
return extraCount ? currentValue > max : currentValue >= max;
}
})
.map((limit) => {
if (!options.suppressLog) {
logger.error({
msg: 'Limit validation failed',
kind: limitKey,
limit,
});
}
return getResultingBehavior(limit, { reason: 'limit', limit: limitKey });
});
}),
)
).flat();
return validateLimits.call(this, limits, options);
}

@ -0,0 +1,28 @@
import type { LicenseBehavior } from '../definition/LicenseBehavior';
/**
* Validates if the current value is over the limit
* @param max The maximum value allowed
* @param currentValue The current value
* @param behavior The behavior to be applied if the limit is reached
* @param extraCount The extra count to be added to the current value
* @returns
* - true if the limit is reached
* - false if the limit is not reached
*/
export function validateLimit(max: number, currentValue: number, behavior: LicenseBehavior, extraCount = 0) {
switch (behavior) {
case 'invalidate_license':
case 'prevent_installation':
case 'disable_modules':
case 'start_fair_policy':
default:
return currentValue > max;
case 'prevent_action':
/**
* if we are validating the current count the limit should be equal or over the max, if we are validating the future count the limit should be over the max
*/
return extraCount ? currentValue > max : currentValue >= max;
}
}

@ -0,0 +1,44 @@
import type { ILicenseV3, LicenseLimitKind } from '../definition/ILicenseV3';
import type { BehaviorWithContext } from '../definition/LicenseBehavior';
import type { LicenseValidationOptions } from '../definition/LicenseValidationOptions';
import { isLimitAllowed, isBehaviorAllowed } from '../isItemAllowed';
import type { LicenseManager } from '../license';
import { logger } from '../logger';
import { getCurrentValueForLicenseLimit } from './getCurrentValueForLicenseLimit';
import { getResultingBehavior } from './getResultingBehavior';
import { validateLimit } from './validateLimit';
export async function validateLimits(
this: LicenseManager,
limits: ILicenseV3['limits'],
options: LicenseValidationOptions,
): Promise<BehaviorWithContext[]> {
const limitKeys = (Object.keys(limits) as LicenseLimitKind[]).filter((limit) => isLimitAllowed(limit, options));
return (
await Promise.all(
limitKeys.map(async (limitKey) => {
// Filter the limit list before running any query in the database so we don't end up loading some value we won't use.
const limitList = limits[limitKey]?.filter(({ behavior, max }) => max >= 0 && isBehaviorAllowed(behavior, options));
if (!limitList?.length) {
return [];
}
const extraCount = options.context?.[limitKey]?.extraCount ?? 0;
const currentValue = (await getCurrentValueForLicenseLimit.call(this, limitKey, options.context?.[limitKey])) + extraCount;
return limitList
.filter(({ max, behavior }) => validateLimit(max, currentValue, behavior, extraCount))
.map((limit) => {
if (!options.suppressLog) {
logger.error({
msg: 'Limit validation failed',
kind: limitKey,
limit,
});
}
return getResultingBehavior(limit, { reason: 'limit', limit: limitKey });
});
}),
)
).flat();
}
Loading…
Cancel
Save