Skip to content

Commit

Permalink
feat: Redact anonymous attributes within feature events
Browse files Browse the repository at this point in the history
  • Loading branch information
keelerm84 committed Feb 6, 2024
1 parent 381f008 commit 3cdf917
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 8 deletions.
1 change: 1 addition & 0 deletions contract-tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ app.get('/', (req, res) => {
'event-sampling',
'strongly-typed',
'inline-context',
'anonymous-redaction',
],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { EventProcessorOptions } from '../../../src/internal/events/EventProcess
import shouldSample from '../../../src/internal/events/sampling';
import BasicLogger from '../../../src/logging/BasicLogger';
import format from '../../../src/logging/format';

Check failure on line 9 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

Delete `t';⏎import·{·LDContextCommon,·LDMultiKindContext·}·from·'../../../dis`
import { LDContextCommon, LDMultiKindContext } from '../../../dist';

jest.mock('../../../src/internal/events/sampling', () => ({
__esModule: true,
Expand Down Expand Up @@ -344,6 +345,89 @@ describe('given an event processor', () => {
]);
});

it('redacts all attributes from anonymous single-kind context for feature events', async () => {
const user = { key: 'user-key', kind: 'user', name: 'Example user', anonymous: true };

Check failure on line 349 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

'user' is already declared in the upper scope on line 27 column 7
const context = Context.fromLDContext(user);

Date.now = jest.fn(() => 1000);
eventProcessor.sendEvent({
kind: 'feature',
creationDate: 1000,
context: context,

Check failure on line 356 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

Expected property shorthand
key: 'flagkey',
version: 11,
variation: 1,
value: 'value',
trackEvents: true,
default: 'default',
samplingRatio: 1,
withReasons: true,
});

await eventProcessor.flush();

const redactedContext = {
'kind': 'user',

Check failure on line 370 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

Replace `'kind'` with `kind`
'key': 'user-key',

Check failure on line 371 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

Replace `'key'` with `key`
'anonymous': true,

Check failure on line 372 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

Replace `'anonymous'` with `anonymous`
'_meta': {

Check failure on line 373 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

Replace `'_meta'` with `_meta`
'redactedAttributes': ['name']

Check failure on line 374 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

Replace `'redactedAttributes':·['name']` with `redactedAttributes:·['name'],`
}

Check failure on line 375 in packages/shared/common/__tests__/internal/events/EventProcessor.test.ts

View workflow job for this annotation

GitHub Actions / build-test-common

Insert `,`
};

const expectedIndexEvent = { ...testIndexEvent, context: user };
const expectedFeatureEvent = { ...makeFeatureEvent(1000, 11, false), context: redactedContext };

expect(mockSendEventData).toBeCalledWith(LDEventType.AnalyticsEvents, [
expectedIndexEvent,
expectedFeatureEvent,
makeSummary(1000, 1000, 1, 11),
]);
});

it('redacts all attributes from anonymous multi-kind context for feature events', async () => {
const user: LDContextCommon = { key: 'user-key', name: 'Example user', anonymous: true };
const org : LDContextCommon= { key: 'org-key', name: 'Example org' };
const multi: LDMultiKindContext = { kind: 'multi', user: user, org: org };
const context = Context.fromLDContext(multi);

Date.now = jest.fn(() => 1000);
eventProcessor.sendEvent({
kind: 'feature',
creationDate: 1000,
context: context,
key: 'flagkey',
version: 11,
variation: 1,
value: 'value',
trackEvents: true,
default: 'default',
samplingRatio: 1,
withReasons: true,
});

await eventProcessor.flush();

const redactedUserContext = {
'key': 'user-key',
'anonymous': true,
'_meta': {
'redactedAttributes': ['name']
}
};

const expectedIndexEvent = { ...testIndexEvent, context: multi };
const expectedFeatureEvent = { ...makeFeatureEvent(1000, 11, false), context: { ...multi, user: redactedUserContext } };
const expectedSummaryEvent = makeSummary(1000, 1000, 1, 11);
expectedSummaryEvent['features']['flagkey']['contextKinds'] = ['user', 'org'];

expect(mockSendEventData).toBeCalledWith(LDEventType.AnalyticsEvents, [
expectedIndexEvent,
expectedFeatureEvent,
expectedSummaryEvent,
]);
});

it('expires debug mode based on client time if client time is later than server time', async () => {
Date.now = jest.fn(() => 2000);

Expand Down
15 changes: 8 additions & 7 deletions packages/shared/common/src/ContextFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,32 +98,33 @@ export default class ContextFilter {
private readonly privateAttributes: AttributeReference[],
) {}

filter(context: Context): any {
filter(context: Context, redactAnonymousAttributes: boolean = false): any {
const contexts = context.getContexts();
if (contexts.length === 1) {
return this.filterSingleKind(context, contexts[0][1], contexts[0][0]);
return this.filterSingleKind(context, contexts[0][1], contexts[0][0], redactAnonymousAttributes);
}
const filteredMulti: any = {
kind: 'multi',
};
contexts.forEach(([kind, single]) => {
filteredMulti[kind] = this.filterSingleKind(context, single, kind);
filteredMulti[kind] = this.filterSingleKind(context, single, kind, redactAnonymousAttributes);
});
return filteredMulti;
}

private getAttributesToFilter(context: Context, single: LDContextCommon, kind: string) {
private getAttributesToFilter(context: Context, single: LDContextCommon, kind: string, redactAllAttributes: boolean) {
return (
this.allAttributesPrivate
redactAllAttributes
? Object.keys(single).map((k) => new AttributeReference(k, true))
: [...this.privateAttributes, ...context.privateAttributes(kind)]
).filter((attr) => !protectedAttributes.some((protectedAttr) => protectedAttr.compare(attr)));
}

private filterSingleKind(context: Context, single: LDContextCommon, kind: string): any {
private filterSingleKind(context: Context, single: LDContextCommon, kind: string, redactAnonymousAttributes: boolean): any {
const redactAllAttributes = this.allAttributesPrivate || (redactAnonymousAttributes && single.anonymous === true);
const { cloned, excluded } = cloneWithRedactions(
single,
this.getAttributesToFilter(context, single, kind),
this.getAttributesToFilter(context, single, kind, redactAllAttributes),
);

if (context.legacy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export default class EventProcessor implements LDEventProcessor {
const out: FeatureOutputEvent = {
kind: debug ? 'debug' : 'feature',
creationDate: event.creationDate,
context: this.contextFilter.filter(event.context),
context: this.contextFilter.filter(event.context, !debug),
key: event.key,
value: event.value,
default: event.default,
Expand Down

0 comments on commit 3cdf917

Please sign in to comment.