diff --git a/frontend/packages/console-shared/src/hooks/__tests__/useTelemetry.spec.ts b/frontend/packages/console-shared/src/hooks/__tests__/useTelemetry.spec.ts index 5da54c202e2..425380fda1f 100644 --- a/frontend/packages/console-shared/src/hooks/__tests__/useTelemetry.spec.ts +++ b/frontend/packages/console-shared/src/hooks/__tests__/useTelemetry.spec.ts @@ -20,8 +20,16 @@ jest.mock('@console/shared/src/hooks/useUserPreference', () => ({ jest.mock('@console/shared/src/hooks/useUser', () => ({ useUser: jest.fn(() => ({ - user: {}, - userResource: {}, + user: { + username: 'shadowman', + }, + userResource: { + metadata: { + annotations: { + 'toolchain.dev.openshift.com/sso-user-id': 'sandbox-user-id-123', + }, + }, + }, userResourceLoaded: true, userResourceError: null, username: 'testuser', @@ -30,22 +38,15 @@ jest.mock('@console/shared/src/hooks/useUser', () => ({ })), })); -const mockUserResource = {}; - const exampleReturnValue = { - accountMail: undefined, + accountMailDomain: '', clusterId: undefined, clusterType: undefined, consoleVersion: undefined, organizationId: undefined, path: undefined, - userResource: mockUserResource, }; -jest.mock('@console/internal/components/utils/k8s-get-hook', () => ({ - useK8sGet: () => [mockUserResource, true], -})); - const mockUserPreference = useUserPreference as jest.Mock; const useResolvedExtensionsMock = useResolvedExtensions as jest.Mock; @@ -220,6 +221,7 @@ describe('useTelemetry', () => { ...exampleReturnValue, clusterType: 'DEVSANDBOX', consoleVersion: 'x.y.z', + sandboxUserId: 'sandbox-user-id-123', }); }); @@ -346,4 +348,52 @@ describe('useTelemetry', () => { fireTelemetryEvent('test 11'); expect(listener).toHaveBeenCalledTimes(1); }); + + it('anonymizes email to only return the domain', () => { + const email = 'shadowman@redhat.com'; + const expectedDomain = 'redhat.com'; + + window.SERVER_FLAGS = { + ...originServerFlags, + telemetry: { + ACCOUNT_MAIL: email, + }, + }; + updateClusterPropertiesFromTests(); + const { result } = renderHook(() => useTelemetry()); + const fireTelemetryEvent = result.current; + fireTelemetryEvent('test 12'); + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).toHaveBeenCalledWith('test 12', { + ...exampleReturnValue, + accountMailDomain: expectedDomain, + }); + + // assert PII is not sent + const callArgs = listener.mock.calls[0][1]; + expect(callArgs.user).toBeUndefined(); + expect(callArgs.userResource).toBeUndefined(); + expect(callArgs.accountMail).toBeUndefined(); + expect(callArgs.sandboxUserId).toBeUndefined(); + }); + + it.each(['invalid-email', 'shadowman@', 'red@hat@redhat.com', '@@', ''])( + 'should not extract domains from invalid emails (%s)', + (email) => { + window.SERVER_FLAGS = { + ...originServerFlags, + telemetry: { + ACCOUNT_MAIL: email, + }, + }; + updateClusterPropertiesFromTests(); + const { result } = renderHook(() => useTelemetry()); + const fireTelemetryEvent = result.current; + fireTelemetryEvent('test 12'); + expect(listener).toHaveBeenCalledWith('test 12', { + ...exampleReturnValue, + accountMailDomain: '', + }); + }, + ); }); diff --git a/frontend/packages/console-shared/src/hooks/useTelemetry.ts b/frontend/packages/console-shared/src/hooks/useTelemetry.ts index e6d809df6c3..6e00d6e74ec 100644 --- a/frontend/packages/console-shared/src/hooks/useTelemetry.ts +++ b/frontend/packages/console-shared/src/hooks/useTelemetry.ts @@ -19,12 +19,13 @@ export interface ClusterProperties { clusterType?: string; consoleVersion?: string; organizationId?: string; - accountMail?: string; + accountMailDomain?: string; } export type TelemetryEventProperties = { user?: UserInfo; - userResource?: UserKind; + /** Only sent if in a sandbox cluster */ + sandboxUserId?: string; } & ClusterProperties & Record; @@ -35,6 +36,11 @@ export interface TelemetryEvent { let telemetryEvents: TelemetryEvent[] = []; +const getEmailDomain = (email: string = ''): string => { + const emailParts = email.split('@'); + return emailParts.length === 2 ? emailParts[1] : ''; +}; + export const getClusterProperties = () => { const clusterProperties: ClusterProperties = {}; clusterProperties.clusterId = window.SERVER_FLAGS.telemetry?.CLUSTER_ID; @@ -46,7 +52,7 @@ export const getClusterProperties = () => { clusterProperties.consoleVersion = window.SERVER_FLAGS.releaseVersion || window.SERVER_FLAGS.consoleVersion; clusterProperties.organizationId = window.SERVER_FLAGS.telemetry?.ORGANIZATION_ID; - clusterProperties.accountMail = window.SERVER_FLAGS.telemetry?.ACCOUNT_MAIL; + clusterProperties.accountMailDomain = getEmailDomain(window.SERVER_FLAGS.telemetry?.ACCOUNT_MAIL); return clusterProperties; }; @@ -66,6 +72,20 @@ let clusterProperties = getClusterProperties(); export const updateClusterPropertiesFromTests = () => (clusterProperties = getClusterProperties()); +const injectSandboxProperties = ( + properties: TelemetryEventProperties, + userResource: UserKind, +): TelemetryEventProperties => { + if (clusterProperties.clusterType === 'DEVSANDBOX') { + return { + ...properties, + sandboxUserId: + userResource?.metadata?.annotations?.['toolchain.dev.openshift.com/sso-user-id'], + }; + } + return properties; +}; + export const useTelemetry = () => { // TODO use usePluginInfo() hook to tell whether all dynamic plugins have been processed // to avoid firing telemetry events multiple times whenever a dynamic plugin loads asynchronously @@ -89,7 +109,10 @@ export const useTelemetry = () => { userResourceIsLoaded ) { telemetryEvents.forEach(({ eventType, event }) => { - extensions.forEach((e) => e.properties.listener(eventType, { ...event, userResource })); + // Sends the telemetry event to all the listeners + extensions.forEach((e) => + e.properties.listener(eventType, injectSandboxProperties(event, userResource)), + ); }); telemetryEvents = []; } @@ -120,7 +143,10 @@ export const useTelemetry = () => { return; } - extensions.forEach((e) => e.properties.listener(eventType, { ...event, userResource })); + // Sends the telemetry event to all the listeners + extensions.forEach((e) => + e.properties.listener(eventType, injectSandboxProperties(event, userResource)), + ); }, [extensions, currentUserPreferenceTelemetryValue, userResource, userResourceIsLoaded], ); diff --git a/frontend/packages/console-telemetry-plugin/src/listeners/segment.ts b/frontend/packages/console-telemetry-plugin/src/listeners/segment.ts index 2a06c3ae885..b77d5921704 100644 --- a/frontend/packages/console-telemetry-plugin/src/listeners/segment.ts +++ b/frontend/packages/console-telemetry-plugin/src/listeners/segment.ts @@ -45,7 +45,7 @@ export const eventListener: TelemetryEventListener = async ( switch (eventType) { case 'identify': { - const { user, userResource, ...otherProperties }: TelemetryEventProperties = properties; + const { user, sandboxUserId, ...otherProperties }: TelemetryEventProperties = properties; const clusterId = otherProperties?.clusterId; const organizationId = otherProperties?.organizationId; const username = user?.username; @@ -65,8 +65,7 @@ export const eventListener: TelemetryEventListener = async ( // anonymize user ID if cluster is not a DEVSANDBOX cluster if (getClusterProperties().clusterType === 'DEVSANDBOX') { - processedUserId = - userResource?.metadata?.annotations?.['toolchain.dev.openshift.com/sso-user-id']; + processedUserId = sandboxUserId; } else { processedUserId = await anonymizeId(userId); }