mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-18 01:18:23 +02:00
Permission guards in existing endpoints interacting with feature toggle configuration (#2418)
This PR adds permission guards for operations. 1. Toggling feature flag 2. Adding a strategy 3. Updating a strategy 4. Deleting a strategy
This commit is contained in:
parent
3624cdc21f
commit
131ebb931a
@ -27,6 +27,7 @@ const T = {
|
|||||||
ROLE_PERMISSION: 'role_permission',
|
ROLE_PERMISSION: 'role_permission',
|
||||||
PERMISSIONS: 'permissions',
|
PERMISSIONS: 'permissions',
|
||||||
PERMISSION_TYPES: 'permission_types',
|
PERMISSION_TYPES: 'permission_types',
|
||||||
|
CHANGE_REQUEST_SETTINGS: 'change_request_settings',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IPermissionRow {
|
interface IPermissionRow {
|
||||||
@ -448,4 +449,17 @@ export class AccessStore implements IAccessStore {
|
|||||||
[destinationEnvironment, sourceEnvironment],
|
[destinationEnvironment, sourceEnvironment],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isChangeRequestsEnabled(
|
||||||
|
project: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const result = await this.db.raw(
|
||||||
|
`SELECT EXISTS(SELECT 1 FROM ${T.CHANGE_REQUEST_SETTINGS}
|
||||||
|
WHERE environment = ? and project = ?) AS present`,
|
||||||
|
[environment, project],
|
||||||
|
);
|
||||||
|
const { present } = result.rows[0];
|
||||||
|
return present;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -542,4 +542,11 @@ export class AccessService {
|
|||||||
await this.validateRoleIsUnique(role.name, existingId);
|
await this.validateRoleIsUnique(role.name, existingId);
|
||||||
return cleanedRole;
|
return cleanedRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isChangeRequestsEnabled(
|
||||||
|
project: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
return this.store.isChangeRequestsEnabled(project, environment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,6 +314,11 @@ class FeatureToggleService {
|
|||||||
const { featureName, projectId, environment } = context;
|
const { featureName, projectId, environment } = context;
|
||||||
await this.validateFeatureContext(context);
|
await this.validateFeatureContext(context);
|
||||||
|
|
||||||
|
if (await this.changeRequestsEnabled(projectId, environment)) {
|
||||||
|
throw new Error(
|
||||||
|
`Strategies can only be created through change requests for ${environment} environment`,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (strategyConfig.constraints?.length > 0) {
|
if (strategyConfig.constraints?.length > 0) {
|
||||||
strategyConfig.constraints = await this.validateConstraints(
|
strategyConfig.constraints = await this.validateConstraints(
|
||||||
strategyConfig.constraints,
|
strategyConfig.constraints,
|
||||||
@ -382,6 +387,12 @@ class FeatureToggleService {
|
|||||||
const existingStrategy = await this.featureStrategiesStore.get(id);
|
const existingStrategy = await this.featureStrategiesStore.get(id);
|
||||||
this.validateFeatureStrategyContext(existingStrategy, context);
|
this.validateFeatureStrategyContext(existingStrategy, context);
|
||||||
|
|
||||||
|
if (await this.changeRequestsEnabled(projectId, environment)) {
|
||||||
|
throw new Error(
|
||||||
|
`Strategies can only be updated through change requests for ${environment} environment`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (existingStrategy.id === id) {
|
if (existingStrategy.id === id) {
|
||||||
if (updates.constraints?.length > 0) {
|
if (updates.constraints?.length > 0) {
|
||||||
updates.constraints = await this.validateConstraints(
|
updates.constraints = await this.validateConstraints(
|
||||||
@ -430,6 +441,12 @@ class FeatureToggleService {
|
|||||||
): Promise<Saved<IStrategyConfig>> {
|
): Promise<Saved<IStrategyConfig>> {
|
||||||
const { projectId, environment, featureName } = context;
|
const { projectId, environment, featureName } = context;
|
||||||
|
|
||||||
|
if (await this.changeRequestsEnabled(projectId, environment)) {
|
||||||
|
throw new Error(
|
||||||
|
`Strategies can only be updated through change requests for ${environment} environment`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const existingStrategy = await this.featureStrategiesStore.get(id);
|
const existingStrategy = await this.featureStrategiesStore.get(id);
|
||||||
this.validateFeatureStrategyContext(existingStrategy, context);
|
this.validateFeatureStrategyContext(existingStrategy, context);
|
||||||
|
|
||||||
@ -482,6 +499,12 @@ class FeatureToggleService {
|
|||||||
const { featureName, projectId, environment } = context;
|
const { featureName, projectId, environment } = context;
|
||||||
this.validateFeatureStrategyContext(existingStrategy, context);
|
this.validateFeatureStrategyContext(existingStrategy, context);
|
||||||
|
|
||||||
|
if (await this.changeRequestsEnabled(projectId, environment)) {
|
||||||
|
throw new Error(
|
||||||
|
`Strategies can only deleted updated through change requests for ${environment} environment`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.featureStrategiesStore.delete(id);
|
await this.featureStrategiesStore.delete(id);
|
||||||
|
|
||||||
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
const tags = await this.tagStore.getAllTagsForFeature(featureName);
|
||||||
@ -903,6 +926,12 @@ class FeatureToggleService {
|
|||||||
createdBy: string,
|
createdBy: string,
|
||||||
user?: User,
|
user?: User,
|
||||||
): Promise<FeatureToggle> {
|
): Promise<FeatureToggle> {
|
||||||
|
if (await this.changeRequestsEnabled(project, environment)) {
|
||||||
|
throw new Error(
|
||||||
|
`Features can only be updated through change requests for ${environment} environment`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const hasEnvironment =
|
const hasEnvironment =
|
||||||
await this.featureEnvironmentStore.featureHasEnvironment(
|
await this.featureEnvironmentStore.featureHasEnvironment(
|
||||||
environment,
|
environment,
|
||||||
@ -928,7 +957,11 @@ class FeatureToggleService {
|
|||||||
if (canAddStrategies) {
|
if (canAddStrategies) {
|
||||||
await this.createStrategy(
|
await this.createStrategy(
|
||||||
getDefaultStrategy(featureName),
|
getDefaultStrategy(featureName),
|
||||||
{ environment, projectId: project, featureName },
|
{
|
||||||
|
environment,
|
||||||
|
projectId: project,
|
||||||
|
featureName,
|
||||||
|
},
|
||||||
createdBy,
|
createdBy,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -961,6 +994,7 @@ class FeatureToggleService {
|
|||||||
}
|
}
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotFoundError(
|
throw new NotFoundError(
|
||||||
`Could not find environment ${environment} for feature: ${featureName}`,
|
`Could not find environment ${environment} for feature: ${featureName}`,
|
||||||
);
|
);
|
||||||
@ -1186,6 +1220,13 @@ class FeatureToggleService {
|
|||||||
});
|
});
|
||||||
return variableVariants.concat(fixedVariants);
|
return variableVariants.concat(fixedVariants);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeRequestsEnabled(
|
||||||
|
project: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
return this.accessService.isChangeRequestsEnabled(project, environment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FeatureToggleService;
|
export default FeatureToggleService;
|
||||||
|
@ -132,4 +132,9 @@ export interface IAccessStore extends Store<IRole, number> {
|
|||||||
sourceEnvironment: string,
|
sourceEnvironment: string,
|
||||||
destinationEnvironment: string,
|
destinationEnvironment: string,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
|
isChangeRequestsEnabled(
|
||||||
|
project: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
7
src/test/fixtures/fake-access-store.ts
vendored
7
src/test/fixtures/fake-access-store.ts
vendored
@ -11,6 +11,13 @@ import {
|
|||||||
import { IPermission } from 'lib/types/model';
|
import { IPermission } from 'lib/types/model';
|
||||||
|
|
||||||
class AccessStoreMock implements IAccessStore {
|
class AccessStoreMock implements IAccessStore {
|
||||||
|
isChangeRequestsEnabled(
|
||||||
|
project: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
addAccessToProject(
|
addAccessToProject(
|
||||||
users: IAccessInfo[],
|
users: IAccessInfo[],
|
||||||
groups: IAccessInfo[],
|
groups: IAccessInfo[],
|
||||||
|
Loading…
Reference in New Issue
Block a user