From 70c7e3f9789c42d54f48bd3b87a8ab4304b5e643 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Mon, 24 Jun 2024 13:48:08 +0200 Subject: [PATCH] feat: Anonimize demo users list flag view (#7432) --- .../fake-feature-search-store.ts | 4 ++-- .../feature-search-controller.ts | 22 ++++++++++++++++-- .../feature-search-store-type.ts | 4 ++-- .../feature-search/feature-search-store.ts | 11 +++++---- .../feature-search/feature.search.e2e.test.ts | 5 ++-- .../feature-toggle-controller.ts | 23 +++++++++++++++++-- .../tests/feature-toggles.auth.e2e.test.ts | 11 +++++++-- src/lib/types/model.ts | 2 +- 8 files changed, 64 insertions(+), 18 deletions(-) diff --git a/src/lib/features/feature-search/fake-feature-search-store.ts b/src/lib/features/feature-search/fake-feature-search-store.ts index 06aff6571b06..945e25247e2b 100644 --- a/src/lib/features/feature-search/fake-feature-search-store.ts +++ b/src/lib/features/feature-search/fake-feature-search-store.ts @@ -1,4 +1,4 @@ -import type { IFeatureOverview } from '../../types'; +import type { IFeatureSearchOverview } from '../../types'; import type { IFeatureSearchParams, IQueryParam, @@ -9,7 +9,7 @@ export default class FakeFeatureSearchStore implements IFeatureSearchStore { searchFeatures( params: IFeatureSearchParams, queryParams: IQueryParam[], - ): Promise<{ features: IFeatureOverview[]; total: number }> { + ): Promise<{ features: IFeatureSearchOverview[]; total: number }> { throw new Error('Method not implemented.'); } } diff --git a/src/lib/features/feature-search/feature-search-controller.ts b/src/lib/features/feature-search/feature-search-controller.ts index ea56a4cc3342..94a42aa3383a 100644 --- a/src/lib/features/feature-search/feature-search-controller.ts +++ b/src/lib/features/feature-search/feature-search-controller.ts @@ -2,6 +2,7 @@ import type { Response } from 'express'; import Controller from '../../routes/controller'; import type { FeatureSearchService, OpenApiService } from '../../services'; import { + type IFeatureSearchOverview, type IFlagResolver, type IUnleashConfig, type IUnleashServices, @@ -12,6 +13,7 @@ import type { Logger } from '../../logger'; import { createResponseSchema, getStandardResponses, + type SearchFeaturesSchema, searchFeaturesSchema, } from '../../openapi'; import type { IAuthRequest } from '../../routes/unleash-types'; @@ -20,6 +22,7 @@ import { featureSearchQueryParameters, } from '../../openapi/spec/feature-search-query-parameters'; import { normalizeQueryParams } from './search-utils'; +import { anonymise } from '../../util'; const PATH = '/features'; @@ -71,9 +74,24 @@ export default class FeatureSearchController extends Controller { }); } + maybeAnonymise( + features: IFeatureSearchOverview[], + ): IFeatureSearchOverview[] { + if (this.flagResolver.isEnabled('anonymiseEventLog')) { + return features.map((feature) => ({ + ...feature, + createdBy: { + ...feature.createdBy, + name: anonymise(feature.createdBy.name), + }, + })); + } + return features; + } + async searchFeatures( req: IAuthRequest, - res: Response, + res: Response, ): Promise { const { query, @@ -131,7 +149,7 @@ export default class FeatureSearchController extends Controller { res, searchFeaturesSchema.$id, serializeDates({ - features, + features: this.maybeAnonymise(features), total, }), ); diff --git a/src/lib/features/feature-search/feature-search-store-type.ts b/src/lib/features/feature-search/feature-search-store-type.ts index 3bfaaa55f6ce..8b5a44fcd9c0 100644 --- a/src/lib/features/feature-search/feature-search-store-type.ts +++ b/src/lib/features/feature-search/feature-search-store-type.ts @@ -2,14 +2,14 @@ import type { IFeatureSearchParams, IQueryParam, } from '../feature-toggle/types/feature-toggle-strategies-store-type'; -import type { IFeatureOverview } from '../../types'; +import type { IFeatureSearchOverview } from '../../types'; export interface IFeatureSearchStore { searchFeatures( params: IFeatureSearchParams, queryParams: IQueryParam[], ): Promise<{ - features: IFeatureOverview[]; + features: IFeatureSearchOverview[]; total: number; }>; } diff --git a/src/lib/features/feature-search/feature-search-store.ts b/src/lib/features/feature-search/feature-search-store.ts index 0937057595d9..49e877857701 100644 --- a/src/lib/features/feature-search/feature-search-store.ts +++ b/src/lib/features/feature-search/feature-search-store.ts @@ -4,7 +4,6 @@ import metricsHelper from '../../util/metrics-helper'; import { DB_TIME } from '../../metric-events'; import type { Logger, LogProvider } from '../../logger'; import type { - IFeatureOverview, IFeatureSearchOverview, IFeatureSearchStore, IFlagResolver, @@ -21,8 +20,8 @@ import { applyGenericQueryParams, applySearchFilters } from './search-utils'; import type { FeatureSearchEnvironmentSchema } from '../../openapi/spec/feature-search-environment-schema'; import { generateImageUrl } from '../../util'; -const sortEnvironments = (overview: IFeatureOverview[]) => { - return overview.map((data: IFeatureOverview) => ({ +const sortEnvironments = (overview: IFeatureSearchOverview[]) => { + return overview.map((data: IFeatureSearchOverview) => ({ ...data, environments: data.environments .filter((f) => f.name) @@ -106,7 +105,7 @@ class FeatureSearchStore implements IFeatureSearchStore { }: IFeatureSearchParams, queryParams: IQueryParam[], ): Promise<{ - features: IFeatureOverview[]; + features: IFeatureSearchOverview[]; total: number; }> { const stopTimer = this.timer('searchFeatures'); @@ -325,7 +324,9 @@ class FeatureSearchStore implements IFeatureSearchStore { rows, featureLifecycleEnabled, ); - const features = sortEnvironments(overview); + const features = sortEnvironments( + overview, + ) as IFeatureSearchOverview[]; return { features, total: Number(rows[0].total) || 0, diff --git a/src/lib/features/feature-search/feature.search.e2e.test.ts b/src/lib/features/feature-search/feature.search.e2e.test.ts index f2201823200d..f0a3ff95ed04 100644 --- a/src/lib/features/feature-search/feature.search.e2e.test.ts +++ b/src/lib/features/feature-search/feature.search.e2e.test.ts @@ -22,6 +22,7 @@ beforeAll(async () => { flags: { strictSchemaValidation: true, featureLifecycle: true, + anonymiseEventLog: true, }, }, }, @@ -186,7 +187,7 @@ test('should search matching features by name', async () => { name: 'my_feature_a', createdBy: { id: 1, - name: 'user@getunleash.io', + name: '3957b71c0@unleash.run', imageUrl: 'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g', }, @@ -195,7 +196,7 @@ test('should search matching features by name', async () => { name: 'my_feature_b', createdBy: { id: 1, - name: 'user@getunleash.io', + name: '3957b71c0@unleash.run', imageUrl: 'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g', }, diff --git a/src/lib/features/feature-toggle/feature-toggle-controller.ts b/src/lib/features/feature-toggle/feature-toggle-controller.ts index 916b44ac72b9..b08c0ebc995e 100644 --- a/src/lib/features/feature-toggle/feature-toggle-controller.ts +++ b/src/lib/features/feature-toggle/feature-toggle-controller.ts @@ -6,6 +6,7 @@ import { CREATE_FEATURE_STRATEGY, DELETE_FEATURE, DELETE_FEATURE_STRATEGY, + type FeatureToggleView, type IFlagResolver, type IUnleashConfig, type IUnleashServices, @@ -52,6 +53,7 @@ import type { UnleashTransaction, } from '../../db/transaction'; import { BadDataError } from '../../error'; +import { anonymise } from '../../util'; interface FeatureStrategyParams { projectId: string; @@ -694,9 +696,25 @@ export default class ProjectFeaturesController extends Controller { ); } + maybeAnonymise(feature: FeatureToggleView): FeatureToggleView { + if ( + this.flagResolver.isEnabled('anonymiseEventLog') && + feature.createdBy + ) { + return { + ...feature, + createdBy: { + ...feature.createdBy, + name: anonymise(feature.createdBy?.name), + }, + }; + } + return feature; + } + async getFeature( req: IAuthRequest, - res: Response, + res: Response, ): Promise { const { featureName, projectId } = req.params; const { variantEnvironments } = req.query; @@ -708,7 +726,8 @@ export default class ProjectFeaturesController extends Controller { environmentVariants: variantEnvironments === 'true', userId: user.id, }); - res.status(200).json(feature); + + res.status(200).json(serializeDates(this.maybeAnonymise(feature))); } async updateFeature( diff --git a/src/lib/features/feature-toggle/tests/feature-toggles.auth.e2e.test.ts b/src/lib/features/feature-toggle/tests/feature-toggles.auth.e2e.test.ts index 85710d4e0e6c..6d32f06d9ca3 100644 --- a/src/lib/features/feature-toggle/tests/feature-toggles.auth.e2e.test.ts +++ b/src/lib/features/feature-toggle/tests/feature-toggles.auth.e2e.test.ts @@ -18,7 +18,14 @@ let db: ITestDb; beforeAll(async () => { db = await dbInit('feature_strategy_auth_api_serial', getLogger); - app = await setupAppWithAuth(db.stores); + app = await setupAppWithAuth(db.stores, { + experimental: { + flags: { + strictSchemaValidation: true, + anonymiseEventLog: true, + }, + }, + }); }); afterEach(async () => { @@ -161,7 +168,7 @@ test('Should read flag creator', async () => { expect(feature.createdBy).toEqual({ id: user.id, - name: 'user@getunleash.io', + name: '3957b71c0@unleash.run', imageUrl: 'https://gravatar.com/avatar/3957b71c0a6d2528f03b423f432ed2efe855d263400f960248a1080493d9d68a?s=42&d=retro&r=g', }); diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index fd6c1dba87f5..ebf347238ce0 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -255,7 +255,7 @@ export type IFeatureSearchOverview = Exclude< createdBy: { id: number; name: string; - imageUrl: string | null; + imageUrl: string; }; };