mirror of
https://github.com/Unleash/unleash.git
synced 2025-11-10 01:19:53 +01:00
feat: ability to disable custom strategies (#10885)
## About the changes This adds the ability to disable custom strategy creation and editing by using two env variables: `UNLEASH_DISABLE_CUSTOM_STRATEGY_CREATION` and `UNLEASH_DISABLE_CUSTOM_STRATEGY_EDITING`. Fixes: #9593 This could be useful if you want to remove the ability to create new custom strategies and rely on the built-in ones
This commit is contained in:
parent
a52ee10827
commit
bf19b62079
@ -29,6 +29,10 @@ exports[`should create default config 1`] = `
|
||||
"enabled": true,
|
||||
"maxAge": 3600000,
|
||||
},
|
||||
"customStrategySettings": {
|
||||
"disableCreation": false,
|
||||
"disableEditing": false,
|
||||
},
|
||||
"dailyMetricsStorageDays": 91,
|
||||
"db": {
|
||||
"acquireConnectionTimeout": 30000,
|
||||
|
||||
@ -585,6 +585,18 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
|
||||
).values(),
|
||||
];
|
||||
|
||||
const customStrategySettings = options.customStrategySettings ?? {
|
||||
disableCreation: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_DISABLE_CUSTOM_STRATEGY_CREATION,
|
||||
false,
|
||||
),
|
||||
|
||||
disableEditing: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_DISABLE_CUSTOM_STRATEGY_EDITING,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
const environmentEnableOverrides = loadEnvironmentEnableOverrides();
|
||||
|
||||
const importSetting: IImportOption = mergeAll([
|
||||
@ -834,6 +846,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
|
||||
userInactivityThresholdInDays,
|
||||
buildDate: process.env.BUILD_DATE,
|
||||
unleashFrontendToken,
|
||||
customStrategySettings,
|
||||
checkDbOnReady,
|
||||
};
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import createStores from '../../../test/fixtures/store.js';
|
||||
import permissions from '../../../test/fixtures/permissions.js';
|
||||
import getApp from '../../app.js';
|
||||
import { createServices } from '../../services/index.js';
|
||||
import { vi } from 'vitest';
|
||||
import { afterEach, vi } from 'vitest';
|
||||
|
||||
async function getSetup() {
|
||||
const randomBase = `/random${Math.round(Math.random() * 1000)}`;
|
||||
@ -25,6 +25,11 @@ async function getSetup() {
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.UNLEASH_DISABLE_CUSTOM_STRATEGY_CREATION;
|
||||
delete process.env.UNLEASH_DISABLE_CUSTOM_STRATEGY_EDITING;
|
||||
});
|
||||
|
||||
test('add version numbers for /strategies', async () => {
|
||||
const { request, base } = await getSetup();
|
||||
return request
|
||||
@ -71,6 +76,20 @@ test('create a new strategy with empty parameters', async () => {
|
||||
.expect(201);
|
||||
});
|
||||
|
||||
test('creating strategies is forbidden when disabled by configuration', async () => {
|
||||
process.env.UNLEASH_DISABLE_CUSTOM_STRATEGY_CREATION = 'true';
|
||||
const { request, base } = await getSetup();
|
||||
|
||||
const response = await request
|
||||
.post(`${base}/api/admin/strategies`)
|
||||
.send({ name: 'LockedStrategy', parameters: [] })
|
||||
.expect(403);
|
||||
|
||||
expect(response.body.message).toContain(
|
||||
'Custom strategy creation is disabled',
|
||||
);
|
||||
});
|
||||
|
||||
test('not be possible to override name', async () => {
|
||||
const { request, base, strategyStore } = await getSetup();
|
||||
strategyStore.createStrategy({ name: 'Testing', parameters: [] });
|
||||
@ -92,6 +111,22 @@ test('update strategy', async () => {
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
test('updating strategies is forbidden when disabled by configuration', async () => {
|
||||
process.env.UNLEASH_DISABLE_CUSTOM_STRATEGY_EDITING = 'true';
|
||||
const { request, base, strategyStore } = await getSetup();
|
||||
const name = 'LockedStrategy';
|
||||
await strategyStore.createStrategy({ name, parameters: [] });
|
||||
|
||||
const response = await request
|
||||
.put(`${base}/api/admin/strategies/${name}`)
|
||||
.send({ name, parameters: [] })
|
||||
.expect(403);
|
||||
|
||||
expect(response.body.message).toContain(
|
||||
'Custom strategy modification is disabled',
|
||||
);
|
||||
});
|
||||
|
||||
test('not update unknown strategy', async () => {
|
||||
const { request, base } = await getSetup();
|
||||
const name = 'UnknownStrat';
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
StrategyUpdatedEvent,
|
||||
} from '../types/index.js';
|
||||
import strategySchema from './strategy-schema.js';
|
||||
import { NameExistsError } from '../error/index.js';
|
||||
import { NameExistsError, OperationDeniedError } from '../error/index.js';
|
||||
|
||||
class StrategyService {
|
||||
private logger: Logger;
|
||||
@ -26,14 +26,20 @@ class StrategyService {
|
||||
|
||||
private eventService: EventService;
|
||||
|
||||
private customStrategySettings: IUnleashConfig['customStrategySettings'];
|
||||
|
||||
constructor(
|
||||
{ strategyStore }: Pick<IUnleashStores, 'strategyStore'>,
|
||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
||||
{
|
||||
getLogger,
|
||||
customStrategySettings,
|
||||
}: Pick<IUnleashConfig, 'getLogger' | 'customStrategySettings'>,
|
||||
eventService: EventService,
|
||||
) {
|
||||
this.strategyStore = strategyStore;
|
||||
this.eventService = eventService;
|
||||
this.logger = getLogger('services/strategy-service.js');
|
||||
this.customStrategySettings = customStrategySettings;
|
||||
}
|
||||
|
||||
async getStrategies(): Promise<IStrategy[]> {
|
||||
@ -103,6 +109,7 @@ class StrategyService {
|
||||
value: IMinimalStrategy,
|
||||
auditUser: IAuditUser,
|
||||
): Promise<IStrategy | undefined> {
|
||||
this.assertCreationAllowed();
|
||||
const strategy = await strategySchema.validateAsync(value);
|
||||
strategy.deprecated = false;
|
||||
await this._validateStrategyName(strategy);
|
||||
@ -120,6 +127,7 @@ class StrategyService {
|
||||
input: IMinimalStrategy,
|
||||
auditUser: IAuditUser,
|
||||
): Promise<void> {
|
||||
this.assertEditingAllowed();
|
||||
const value = await strategySchema.validateAsync(input);
|
||||
const strategy = await this.strategyStore.get(input.name);
|
||||
await this._validateEditable(strategy);
|
||||
@ -155,5 +163,21 @@ class StrategyService {
|
||||
throw new Error(`Cannot edit strategy ${strategy?.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
private assertCreationAllowed(): void {
|
||||
if (this.customStrategySettings?.disableCreation) {
|
||||
throw new OperationDeniedError(
|
||||
'Custom strategy creation is disabled by configuration.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private assertEditingAllowed(): void {
|
||||
if (this.customStrategySettings?.disableEditing) {
|
||||
throw new OperationDeniedError(
|
||||
'Custom strategy modification is disabled by configuration.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
export default StrategyService;
|
||||
|
||||
@ -118,6 +118,11 @@ export interface IClientCachingOption {
|
||||
maxAge: number;
|
||||
}
|
||||
|
||||
export interface ICustomStrategySettings {
|
||||
disableCreation: boolean;
|
||||
disableEditing: boolean;
|
||||
}
|
||||
|
||||
export interface ResourceLimits {
|
||||
apiTokens: number;
|
||||
constraints: number;
|
||||
@ -175,6 +180,7 @@ export interface IUnleashOptions {
|
||||
resourceLimits?: Partial<ResourceLimits>;
|
||||
userInactivityThresholdInDays?: number;
|
||||
unleashFrontendToken?: string;
|
||||
customStrategySettings?: ICustomStrategySettings;
|
||||
checkDbOnReady?: boolean;
|
||||
}
|
||||
|
||||
@ -302,5 +308,6 @@ export interface IUnleashConfig {
|
||||
userInactivityThresholdInDays: number;
|
||||
buildDate?: string;
|
||||
unleashFrontendToken?: string;
|
||||
customStrategySettings?: ICustomStrategySettings;
|
||||
checkDbOnReady?: boolean;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user