1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

fix: Issue #1444 - API import with drop=true deletes existing client keys (#1668)

* fix: Does not delete api_tokens on drop-Import

* feat: Cleans unused apiTokens on environment import

* refactor: Moves ALL_PROJECTS and ALL_ENVIRONMENTS to constants

* refactor: Renames migration 20220528143630 for a more precise name

* refactor: Removes unecessary console.log

* fix: Adds correct down-script for migration 20220528143630
This commit is contained in:
Dennis Szczepanski 2022-06-09 16:56:13 +02:00 committed by GitHub
parent d7c450abf8
commit 7ead887147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 198 additions and 10 deletions

View File

@ -0,0 +1,19 @@
version: '3.1'
services:
postgres:
image: postgres:alpine3.15
environment:
POSTGRES_USER: unleash_user
POSTGRES_PASSWORD: passord
POSTGRES_DB: unleash
ports:
- 5432:5432
pgadmin:
image: dpage/pgadmin4:6.8
environment:
PGADMIN_DEFAULT_EMAIL: 'admin@admin.com'
PGADMIN_DEFAULT_PASSWORD: 'admin'
ports:
- 8080:80

View File

@ -11,7 +11,7 @@ import {
IApiTokenCreate,
isAllProjects,
} from '../types/models/api-token';
import { ALL_PROJECTS } from '../../lib/services/access-service';
import { ALL_PROJECTS } from '../util/constants';
const TABLE = 'api_tokens';
const API_LINK_TABLE = 'api_token_project';

View File

@ -24,13 +24,10 @@ import NameExistsError from '../error/name-exists-error';
import { IEnvironmentStore } from 'lib/types/stores/environment-store';
import RoleInUseError from '../error/role-in-use-error';
import { roleSchema } from '../schema/role-schema';
import { CUSTOM_ROLE_TYPE } from '../util/constants';
import { CUSTOM_ROLE_TYPE, ALL_PROJECTS, ALL_ENVS } from '../util/constants';
import { DEFAULT_PROJECT } from '../types/project';
import InvalidOperationError from '../error/invalid-operation-error';
export const ALL_PROJECTS = '*';
export const ALL_ENVS = '*';
const { ADMIN } = permissions;
const PROJECT_ADMIN = [

View File

@ -46,10 +46,11 @@ import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-stor
import { IEnvironmentStore } from '../types/stores/environment-store';
import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-store';
import { IUnleashStores } from '../types/stores';
import { DEFAULT_ENV } from '../util/constants';
import { DEFAULT_ENV, ALL_ENVS } from '../util/constants';
import { GLOBAL_ENV } from '../types/environment';
import { ISegmentStore } from '../types/stores/segment-store';
import { PartialSome } from '../types/partial';
import { IApiTokenStore } from 'lib/types/stores/api-token-store';
export interface IBackupOption {
includeFeatureToggles: boolean;
@ -92,6 +93,8 @@ export default class StateService {
private segmentStore: ISegmentStore;
private apiTokenStore: IApiTokenStore;
constructor(
stores: IUnleashStores,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
@ -107,6 +110,7 @@ export default class StateService {
this.featureTagStore = stores.featureTagStore;
this.environmentStore = stores.environmentStore;
this.segmentStore = stores.segmentStore;
this.apiTokenStore = stores.apiTokenStore;
this.logger = getLogger('services/state-service.js');
}
@ -433,6 +437,15 @@ export default class StateService {
data: env,
}));
await this.eventStore.batchStore(importedEnvironmentEvents);
const apiTokens = await this.apiTokenStore.getAll();
const envNames = importedEnvs.map((env) => env.name);
apiTokens
.filter((apiToken) => !(apiToken.environment === ALL_ENVS))
.filter((apiToken) => !envNames.includes(apiToken.environment))
.forEach((apiToken) =>
this.apiTokenStore.delete(apiToken.secret),
);
}
}

View File

@ -1,5 +1,8 @@
export const DEFAULT_ENV = 'default';
export const ALL_PROJECTS = '*';
export const ALL_ENVS = '*';
export const ROOT_PERMISSION_TYPE = 'root';
export const ENVIRONMENT_PERMISSION_TYPE = 'environment';
export const PROJECT_PERMISSION_TYPE = 'project';

View File

@ -0,0 +1,19 @@
'use strict';
exports.up = function (db, cb) {
db.runSql(
`
ALTER TABLE api_tokens DROP CONSTRAINT api_tokens_environment_fkey;
`,
cb,
);
};
exports.down = function (db, cb) {
db.runSql(
`
ALTER TABLE api_tokens ADD CONSTRAINT api_tokens_environment_fkey FOREIGN KEY(environment) REFERENCES environments(name) ON DELETE CASCADE;
`,
cb,
);
};

View File

@ -3,6 +3,7 @@ import { IUnleashTest, setupApp } from '../../helpers/test-helper';
import getLogger from '../../../fixtures/no-logger';
import { DEFAULT_ENV } from '../../../../lib/util/constants';
import { collectIds } from '../../../../lib/util/collect-ids';
import { ApiTokenType } from '../../../../lib/types/models/api-token';
const importData = require('../../../examples/import.json');
@ -337,3 +338,105 @@ test(`should import segments and connect them to feature strategies`, async () =
expect(activeSegments.length).toEqual(1);
expect(collectIds(activeSegments)).toEqual([1]);
});
test(`should not delete api_tokens on import when drop-flag is set`, async () => {
const projectId = 'reimported-project';
const environment = 'reimported-environment';
const apiTokenName = 'not-dropped-token';
const featureName = 'reimportedFeature';
const userName = 'apiTokens-user';
await db.stores.environmentStore.create({
name: environment,
type: 'test',
});
await db.stores.projectStore.create({
name: projectId,
id: projectId,
description: 'Project for export',
});
await app.services.environmentService.addEnvironmentToProject(
environment,
projectId,
);
await app.services.featureToggleServiceV2.createFeatureToggle(
projectId,
{
type: 'Release',
name: featureName,
description: 'Feature for export',
},
userName,
);
await app.services.featureToggleServiceV2.createStrategy(
{
name: 'default',
constraints: [
{ contextName: 'userId', operator: 'IN', values: ['123'] },
],
parameters: {},
},
{
projectId,
featureName,
environment,
},
userName,
);
await app.services.apiTokenService.createApiTokenWithProjects({
username: apiTokenName,
type: ApiTokenType.CLIENT,
environment: environment,
projects: [projectId],
});
const data = await app.services.stateService.export({});
await app.services.stateService.import({
data,
dropBeforeImport: true,
keepExisting: false,
userName: userName,
});
const apiTokens = await app.services.apiTokenService.getAllTokens();
expect(apiTokens.length).toEqual(1);
expect(apiTokens[0].username).toBe(apiTokenName);
});
test(`should clean apitokens for not existing environment after import with drop`, async () => {
const projectId = 'not-reimported-project';
const environment = 'not-reimported-environment';
const apiTokenName = 'dropped-token';
await db.stores.environmentStore.create({
name: environment,
type: 'test',
});
await db.stores.projectStore.create({
name: projectId,
id: projectId,
description: 'Project for export',
});
await app.services.environmentService.addEnvironmentToProject(
environment,
projectId,
);
await app.services.apiTokenService.createApiTokenWithProjects({
username: apiTokenName,
type: ApiTokenType.CLIENT,
environment: environment,
projects: [projectId],
});
await app.request
.post('/api/admin/state/import?drop=true')
.attach('file', 'src/test/examples/v3-minimal.json')
.expect(202);
const apiTokens = await app.services.apiTokenService.getAllTokens();
console.log(apiTokens);
expect(apiTokens.length).toEqual(0);
});

View File

@ -2,10 +2,7 @@ import dbInit, { ITestDb } from '../helpers/database-init';
import getLogger from '../../fixtures/no-logger';
// eslint-disable-next-line import/no-unresolved
import {
AccessService,
ALL_PROJECTS,
} from '../../../lib/services/access-service';
import { AccessService } from '../../../lib/services/access-service';
import * as permissions from '../../../lib/types/permissions';
import { RoleName } from '../../../lib/types/model';
@ -14,6 +11,7 @@ import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import ProjectService from '../../../lib/services/project-service';
import { createTestConfig } from '../../config/test-config';
import { DEFAULT_PROJECT } from '../../../lib/types/project';
import { ALL_PROJECTS } from '../../../lib/util/constants';
import { SegmentService } from '../../../lib/services/segment-service';
let db: ITestDb;

View File

@ -0,0 +1,36 @@
{
"version": 3,
"projects": [
{
"id": "default",
"name": "Default",
"description": "Default project",
"createdAt": "2022-05-19T18:28:31.927Z",
"health": 100,
"updatedAt": "2022-05-19T20:28:32.736Z"
}
],
"environments": [
{
"name": "default",
"type": "production",
"sortOrder": 1,
"enabled": false,
"protected": true
},
{
"name": "development",
"type": "development",
"sortOrder": 100,
"enabled": true,
"protected": false
},
{
"name": "production",
"type": "production",
"sortOrder": 200,
"enabled": true,
"protected": false
}
]
}