1
0
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:
sjaanus 2022-11-14 14:05:26 +01:00 committed by GitHub
parent 3624cdc21f
commit 131ebb931a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 1 deletions

View File

@ -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;
}
} }

View File

@ -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);
}
} }

View File

@ -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;

View File

@ -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>;
} }

View File

@ -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[],