diff --git a/package.json b/package.json index 56ca4accefd05c35ae41a400081c64ed0bfa4a87..eea7bad80518c70155366d1cb51331c5591551d2 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ }, "dependencies": { "applicationinsights": "1.0.8", - "getmac": "1.4.1", "graceful-fs": "4.1.11", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.1", diff --git a/src/typings/getmac.d.ts b/src/typings/getmac.d.ts deleted file mode 100644 index e93b8135456f36c16ca331a73b5822042c2c08fc..0000000000000000000000000000000000000000 --- a/src/typings/getmac.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module getmac { - export function getMac(callback: (error: Error, macAddress: string) => void): void; -} - -declare module 'getmac' { - export = getmac; -} \ No newline at end of file diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index 6f72afeed792182dac2ccc2933c167d50082ab9c..c2faa20632fefd8bb9207b662b5442a18bcee3a1 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -7,6 +7,7 @@ import * as errors from 'vs/base/common/errors'; import * as uuid from 'vs/base/common/uuid'; import { networkInterfaces } from 'os'; import { TernarySearchTree } from 'vs/base/common/map'; +import { getMac } from 'vs/base/node/macAddress'; // http://www.techrepublic.com/blog/data-center/mac-address-scorecard-for-common-virtual-machine-platforms/ // VMware ESX 3, Server, Workstation, Player 00-50-56, 00-0C-29, 00-05-69 @@ -76,35 +77,25 @@ export const virtualMachineHint: { value(): number } = new class { }; let machineId: Promise; -export function getMachineId(): Promise { - return machineId || (machineId = getMacMachineId() - .then(id => id || uuid.generateUuid())); // fallback, generate a UUID -} +export async function getMachineId(): Promise { + if (!machineId) { + machineId = (async () => { + const id = await getMacMachineId(); -function getMacMachineId(): Promise { - return new Promise(resolve => { - Promise.all([import('crypto'), import('getmac')]).then(([crypto, getmac]) => { - try { - getmac.getMac((error, macAddress) => { - if (!error) { - resolve(crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex')); - } else { - resolve(undefined); - } - }); + return id || uuid.generateUuid(); // fallback, generate a UUID + })(); + } - // Timeout due to hang with reduced privileges #58392 - // TODO@sbatten: Remove this when getmac is patched - setTimeout(() => { - resolve(undefined); - }, 10000); - } catch (err) { - errors.onUnexpectedError(err); - resolve(undefined); - } - }, err => { - errors.onUnexpectedError(err); - resolve(undefined); - }); - }); + return machineId; +} + +async function getMacMachineId(): Promise { + try { + const crypto = await import('crypto'); + const macAddress = await getMac(); + return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'); + } catch (err) { + errors.onUnexpectedError(err); + return undefined; + } } diff --git a/src/vs/base/node/macAddress.ts b/src/vs/base/node/macAddress.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd36be223449e0f750fad46231335dc6d42143e1 --- /dev/null +++ b/src/vs/base/node/macAddress.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { exec } from 'child_process'; +import { isWindows } from 'vs/base/common/platform'; + +const cmdline = { + windows: 'getmac.exe', + unix: '/sbin/ifconfig -a || /sbin/ip link' +}; + +const invalidMacAddresses = [ + '00:00:00:00:00:00', + 'ff:ff:ff:ff:ff:ff', + 'ac:de:48:00:11:22' +]; + +function validateMacAddress(candidate: string): boolean { + let tempCandidate = candidate.replace(/\-/g, ':').toLowerCase(); + for (let invalidMacAddress of invalidMacAddresses) { + if (invalidMacAddress === tempCandidate) { + return false; + } + } + + return true; +} + +export function getMac(): Promise { + return new Promise(async (resolve, reject) => { + const timeout = setTimeout(() => reject('Unable to retrieve mac address (timeout after 10s)'), 10000); + + try { + resolve(await doGetMac()); + } catch (error) { + reject(error); + } finally { + clearTimeout(timeout); + } + }); +} + +function doGetMac(): Promise { + return new Promise((resolve, reject) => { + try { + exec(isWindows ? cmdline.windows : cmdline.unix, { timeout: 10000 }, (err, stdout, stdin) => { + if (err) { + return reject(`Unable to retrieve mac address (${err.toString()})`); + } else { + const regex = /(?:[a-f\d]{2}[:\-]){5}[a-f\d]{2}/gi; + + let match; + while ((match = regex.exec(stdout)) !== null) { + const macAddressCandidate = match[0]; + if (validateMacAddress(macAddressCandidate)) { + return resolve(macAddressCandidate); + } + } + + return reject('Unable to retrieve mac address (unexpected format)'); + } + }); + } catch (err) { + reject(err); + } + }); +} \ No newline at end of file diff --git a/src/vs/base/test/node/id.test.ts b/src/vs/base/test/node/id.test.ts index 689bb126d6b565044d0250b694b543e2095fd290..637afa5b550a26ef27caf8f8cdffe23f00defc46 100644 --- a/src/vs/base/test/node/id.test.ts +++ b/src/vs/base/test/node/id.test.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as getmac from 'getmac'; import { getMachineId } from 'vs/base/node/id'; +import { getMac } from 'vs/base/node/macAddress'; suite('ID', () => { @@ -16,9 +16,7 @@ suite('ID', () => { }); test('getMac', () => { - return new Promise((resolve, reject) => { - getmac.getMac((err, macAddress) => err ? reject(err) : resolve(macAddress)); - }).then(macAddress => { + return getMac().then(macAddress => { assert.ok(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(macAddress), `Expected a MAC address, got: ${macAddress}`); }); }); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index f70eece8b301536d5538ea1b9e947b9045da4608..cd81cc3dbb2d97afa3db9522575dfa897f7a593f 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -90,6 +90,7 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro export class CodeApplication extends Disposable { private static readonly MACHINE_ID_KEY = 'telemetry.machineId'; + private static readonly TRUE_MACHINE_ID_KEY = 'telemetry.trueMachineId'; private windowsMainService: IWindowsMainService | undefined; @@ -363,8 +364,8 @@ export class CodeApplication extends Disposable { // Resolve unique machine ID this.logService.trace('Resolving machine identifier...'); - const machineId = await this.resolveMachineId(); - this.logService.trace(`Resolved machine identifier: ${machineId}`); + const { machineId, trueMachineId } = await this.resolveMachineId(); + this.logService.trace(`Resolved machine identifier: ${machineId} (trueMachineId: ${trueMachineId})`); // Spawn shared process after the first window has opened and 3s have passed const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); @@ -378,7 +379,7 @@ export class CodeApplication extends Disposable { }); // Services - const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessClient); + const appInstantiationService = await this.createServices(machineId, trueMachineId, sharedProcess, sharedProcessClient); // Create driver if (this.environmentService.driverHandle) { @@ -404,7 +405,7 @@ export class CodeApplication extends Disposable { } } - private async resolveMachineId(): Promise { + private async resolveMachineId(): Promise<{ machineId: string, trueMachineId?: string }> { // We cache the machineId for faster lookups on startup // and resolve it only once initially if not cached @@ -415,10 +416,21 @@ export class CodeApplication extends Disposable { this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId); } - return machineId; + // Check if machineId is hashed iBridge Device + let trueMachineId: string | undefined; + if (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead') { + trueMachineId = this.stateService.getItem(CodeApplication.TRUE_MACHINE_ID_KEY); + if (!trueMachineId) { + trueMachineId = await getMachineId(); + + this.stateService.setItem(CodeApplication.TRUE_MACHINE_ID_KEY, trueMachineId); + } + } + + return { machineId, trueMachineId }; } - private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessClient: Promise>): Promise { + private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessClient: Promise>): Promise { const services = new ServiceCollection(); // Files @@ -473,7 +485,7 @@ export class CodeApplication extends Disposable { const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)); const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath); const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath]; - const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths }; + const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId }; services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); } else { diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 30fb12f5f79731de0611ad8cb3188393a7a4c524..2e7a4bb00d15c0b9fbbb3cf629bfdfefcfa86752 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -19,6 +19,7 @@ export interface ITelemetryServiceConfig { appender: ITelemetryAppender; commonProperties?: Promise<{ [name: string]: any }>; piiPaths?: string[]; + trueMachineId?: string; } export class TelemetryService implements ITelemetryService { @@ -74,6 +75,15 @@ export class TelemetryService implements ITelemetryService { } */ this.publicLog('machineIdFallback', { usingFallbackGuid: !isHashedId }); + + if (config.trueMachineId) { + /* __GDPR__ + "machineIdDisambiguation" : { + "correctedMachineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" } + } + */ + this.publicLog('machineIdDisambiguation', { correctedMachineId: config.trueMachineId }); + } }); } } diff --git a/yarn.lock b/yarn.lock index b5d75eeb16966d2fc275461bcd2a61dfa731f54f..c8559584760758f82f8b237e0a409c38023db2b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2421,14 +2421,6 @@ each-props@^1.3.0: is-plain-object "^2.0.1" object.defaults "^1.1.0" -eachr@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/eachr/-/eachr-3.2.0.tgz#2c35e43ea086516f7997cf80b7aa64d55a4a4484" - integrity sha1-LDXkPqCGUW95l8+At6pk1VpKRIQ= - dependencies: - editions "^1.1.1" - typechecker "^4.3.0" - ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" @@ -2446,11 +2438,6 @@ ecstatic@^3.0.0: minimist "^1.1.0" url-join "^2.0.5" -editions@^1.1.1, editions@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" - integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg== - editorconfig@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.0.tgz#b6dd4a0b6b9e76ce48e066bdc15381aebb8804fd" @@ -3078,15 +3065,6 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-opts@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/extract-opts/-/extract-opts-3.3.1.tgz#5abbedc98c0d5202e3278727f9192d7e086c6be1" - integrity sha1-WrvtyYwNUgLjJ4cn+Rktfghsa+E= - dependencies: - eachr "^3.2.0" - editions "^1.1.1" - typechecker "^4.3.0" - extract-zip@^1.6.5: version "1.6.6" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.6.tgz#1290ede8d20d0872b429fd3f351ca128ec5ef85c" @@ -3579,14 +3557,6 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= -getmac@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/getmac/-/getmac-1.4.1.tgz#cfefcb3ee7d7a73cba5292129cb100c19afbe17a" - integrity sha512-mQp+8D+grQX0gG8EJn6VfH0PxE56ZKNsTguOMxPShAiVk9lvH8Ey36eXepG705Ac1HCsvaSrQ/6bPHZ0++F/Mg== - dependencies: - editions "^1.3.4" - extract-opts "^3.2.0" - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -9087,13 +9057,6 @@ type-is@~1.6.15: media-typer "0.3.0" mime-types "~2.1.15" -typechecker@^4.3.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-4.5.0.tgz#c382920097812364bbaf4595b0ab6588244117a6" - integrity sha512-bqPE/ck3bVIaXP7gMKTKSHrypT32lpYTpiqzPYeYzdSQnmaGvaGhy7TnN/M/+5R+2rs/kKcp9ZLPRp/Q9Yj+4w== - dependencies: - editions "^1.3.4" - typed-rest-client@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/typed-rest-client/-/typed-rest-client-0.9.0.tgz#f768cc0dc3f4e950f06e04825c36b3e7834aa1f2"