Bug 1489531 Expose telemetry client_id hash to about:addons via cookie r=Gijs,chutten

Differential Revision: https://phabricator.services.mozilla.com/D9317

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Shane Caraveo 2018-11-26 15:26:39 +00:00
Родитель 7ba0490aba
Коммит dfbb130d83
7 изменённых файлов: 254 добавлений и 0 удалений

Просмотреть файл

@ -1794,3 +1794,8 @@ pref("toolkit.coverage.endpoint.base", "https://coverage.mozilla.org");
#if defined(NIGHTLY_BUILD) && defined(MOZ_LIBPRIO)
pref("prio.enabled", true);
#endif
// Discovery prefs
pref("browser.discovery.enabled", false);
pref("browser.discovery.containers.enabled", true);
pref("browser.discovery.sites", "addons.mozilla.org");

Просмотреть файл

@ -391,6 +391,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
CustomizableUI: "resource:///modules/CustomizableUI.jsm",
DateTimePickerParent: "resource://gre/modules/DateTimePickerParent.jsm",
Discovery: "resource:///modules/Discovery.jsm",
ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
Feeds: "resource:///modules/Feeds.jsm",
FileSource: "resource://gre/modules/L10nRegistry.jsm",
@ -1671,6 +1672,10 @@ BrowserGlue.prototype = {
LiveBookmarkMigrator.migrate().catch(Cu.reportError);
});
}
Services.tm.idleDispatchToMainThread(() => {
Discovery.update();
});
},
/**

Просмотреть файл

@ -0,0 +1,119 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = [
"Discovery",
];
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "ClientID",
"resource://gre/modules/ClientID.jsm");
ChromeUtils.defineModuleGetter(this, "ContextualIdentityService",
"resource://gre/modules/ContextualIdentityService.jsm");
const RECOMMENDATION_ENABLED = "browser.discovery.enabled";
const TELEMETRY_ENABLED = "datareporting.healthreport.uploadEnabled";
const TAAR_COOKIE_NAME = "taarId";
const Discovery = {
set enabled(val) {
val = !!val;
if (val && !gTelemetryEnabled) {
throw Error("unable to turn on recommendations");
}
Services.prefs.setBoolPref(RECOMMENDATION_ENABLED, val);
},
get enabled() {
return gTelemetryEnabled && gRecommendationEnabled;
},
reset() {
return DiscoveryInternal.update(true);
},
update() {
return DiscoveryInternal.update();
},
};
XPCOMUtils.defineLazyPreferenceGetter(this, "gRecommendationEnabled",
RECOMMENDATION_ENABLED, false,
Discovery.update);
XPCOMUtils.defineLazyPreferenceGetter(this, "gTelemetryEnabled",
TELEMETRY_ENABLED, false,
Discovery.update);
XPCOMUtils.defineLazyPreferenceGetter(this, "gCachedClientID",
"toolkit.telemetry.cachedClientID", "",
Discovery.reset);
XPCOMUtils.defineLazyPreferenceGetter(this, "gContainersEnabled",
"browser.discovery.containers.enabled", false,
Discovery.reset);
Services.obs.addObserver(Discovery.update, "contextual-identity-created");
const DiscoveryInternal = {
get sites() {
delete this.sites;
this.sites = Services.prefs.getCharPref("browser.discovery.sites", "").split(",");
return this.sites;
},
getContextualIDs() {
// There is never a zero id, this is just for use in update.
let IDs = [0];
if (gContainersEnabled) {
ContextualIdentityService.getPublicIdentities().forEach(identity => {
IDs.push(identity.userContextId);
});
}
return IDs;
},
async update(reset = false) {
if (reset || !Discovery.enabled) {
for (let site of this.sites) {
Services.cookies.remove(site, TAAR_COOKIE_NAME, "/", false, {});
ContextualIdentityService.getPublicIdentities().forEach(identity => {
let {userContextId} = identity;
Services.cookies.remove(site, TAAR_COOKIE_NAME, "/", false, {userContextId});
});
}
}
if (Discovery.enabled) {
// If the client id is not cached, wait for the notification that it is
// cached. This will happen shortly after startup in TelemetryController.jsm.
// When that happens, we'll get a pref notification for the cached id,
// which will call update again.
if (!gCachedClientID) {
return;
}
let id = await ClientID.getClientIdHash();
for (let site of this.sites) {
// This cookie gets tied down as much as possible. Specifically,
// SameSite, Secure, HttpOnly and non-PrivateBrowsing.
for (let userContextId of this.getContextualIDs()) {
let originAttributes = {privateBrowsingId: 0};
if (userContextId > 0) {
originAttributes.userContextId = userContextId;
}
if (Services.cookies.cookieExists(site, "/", TAAR_COOKIE_NAME, originAttributes)) {
continue;
}
Services.cookies.add(site, "/", TAAR_COOKIE_NAME, id,
true, // secure
true, // httpOnly
true, // session
Date.now(),
originAttributes,
Ci.nsICookie2.SAMESITE_LAX);
}
}
}
},
};

Просмотреть файл

@ -133,6 +133,7 @@ EXTRA_JS_MODULES += [
'ContentMetaHandler.jsm',
'ContentObservers.js',
'ContentSearch.jsm',
'Discovery.jsm',
'ExtensionsUI.jsm',
'FaviconLoader.jsm',
'Feeds.jsm',

Просмотреть файл

@ -0,0 +1,101 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* globals ChromeUtils, Assert, add_task */
"use strict";
// ClientID fails without...
do_get_profile();
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://testing-common/TestUtils.jsm");
ChromeUtils.import("resource://gre/modules/ClientID.jsm");
ChromeUtils.import("resource:///modules/Discovery.jsm");
ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm");
const TAAR_COOKIE_NAME = "taarId";
add_task(async function test_discovery() {
let uri = Services.io.newURI("https://example.com/foobar");
// Ensure the prefs we need
Services.prefs.setBoolPref("browser.discovery.enabled", true);
Services.prefs.setBoolPref("browser.discovery.containers.enabled", true);
Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true);
Services.prefs.setCharPref("browser.discovery.sites", uri.host);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("browser.discovery.enabled");
Services.prefs.clearUserPref("browser.discovery.containers.enabled");
Services.prefs.clearUserPref("browser.discovery.sites");
Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
});
// This is normally initialized by telemetry, force id creation. This results
// in Discovery setting the cookie.
await ClientID.getClientID();
await Discovery.update();
ok(Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {}), "cookie exists");
ok(!Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {privateBrowsingId: 1}), "no private cookie exists");
ContextualIdentityService.getPublicIdentities().forEach(identity => {
let {userContextId} = identity;
equal(Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {userContextId}), identity.public, "cookie exists");
});
// Test the addition of a new container.
let changed = TestUtils.topicObserved("cookie-changed", (subject, data) => {
let cookie = subject.QueryInterface(Ci.nsICookie2);
equal(cookie.name, TAAR_COOKIE_NAME, "taar cookie exists");
equal(cookie.host, uri.host, "cookie exists for host");
equal(cookie.originAttributes.userContextId, container.userContextId, "cookie userContextId is correct");
return true;
});
let container = ContextualIdentityService.create("New Container", "Icon", "Color");
await changed;
// Test disabling
Discovery.enabled = false;
// Wait for the update to remove the cookie.
await TestUtils.waitForCondition(() => {
return !Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {});
});
ContextualIdentityService.getPublicIdentities().forEach(identity => {
let {userContextId} = identity;
ok(!Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {userContextId}), "no cookie exists");
});
// turn off containers
Services.prefs.setBoolPref("browser.discovery.containers.enabled", false);
Discovery.enabled = true;
await TestUtils.waitForCondition(() => {
return Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {});
});
// make sure we did not set cookies on containers
ContextualIdentityService.getPublicIdentities().forEach(identity => {
let {userContextId} = identity;
ok(!Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {userContextId}), "no cookie exists");
});
// Make sure clientId changes update discovery
changed = TestUtils.topicObserved("cookie-changed", (subject, data) => {
if (data !== "added") {
return false;
}
let cookie = subject.QueryInterface(Ci.nsICookie2);
equal(cookie.name, TAAR_COOKIE_NAME, "taar cookie exists");
equal(cookie.host, uri.host, "cookie exists for host");
return true;
});
ClientID.resetClientID();
await changed;
// Make sure disabling telemetry disables discovery.
Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", false);
await TestUtils.waitForCondition(() => {
return !Services.cookies.cookieExists(uri.host, "/", TAAR_COOKIE_NAME, {});
});
});

Просмотреть файл

@ -9,3 +9,4 @@ skip-if = toolkit == 'android'
[test_Sanitizer_interrupted.js]
[test_SitePermissions.js]
[test_LaterRun.js]
[test_discovery.js]

Просмотреть файл

@ -21,6 +21,10 @@ ChromeUtils.defineModuleGetter(this, "CommonUtils",
ChromeUtils.defineModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyGetter(this, "CryptoHash", () => {
return Components.Constructor("@mozilla.org/security/hash;1", "nsICryptoHash", "initWithString");
});
XPCOMUtils.defineLazyGetter(this, "gDatareportingPath", () => {
return OS.Path.join(OS.Constants.Path.profileDir, "datareporting");
});
@ -82,6 +86,10 @@ var ClientID = Object.freeze({
return ClientIDImpl.getCachedClientID();
},
async getClientIdHash() {
return ClientIDImpl.getClientIdHash();
},
/**
* Set a specific client id asynchronously, writing it to disk
* and updating the cached version.
@ -118,6 +126,7 @@ var ClientID = Object.freeze({
var ClientIDImpl = {
_clientID: null,
_clientIDHash: null,
_loadClientIdTask: null,
_saveClientIdTask: null,
_removeClientIdTask: null,
@ -243,6 +252,16 @@ var ClientIDImpl = {
return id;
},
async getClientIdHash() {
if (!this._clientIDHash) {
let byteArr = new TextEncoder().encode(await this.getClientID());
let hash = new CryptoHash("sha256");
hash.update(byteArr, byteArr.length);
this._clientIDHash = CommonUtils.bytesAsHex(hash.finish(false));
}
return this._clientIDHash;
},
/*
* Resets the provider. This is for testing only.
*/
@ -250,6 +269,7 @@ var ClientIDImpl = {
await this._loadClientIdTask;
await this._saveClientIdTask;
this._clientID = null;
this._clientIDHash = null;
},
async setClientID(id) {
@ -265,6 +285,7 @@ var ClientIDImpl = {
async _doRemoveClientID() {
// Reset stored id.
this._clientID = null;
this._clientIDHash = null;
// Clear the client id from the preference cache.
Services.prefs.clearUserPref(PREF_CACHED_CLIENTID);
@ -308,6 +329,7 @@ var ClientIDImpl = {
}
this._clientID = id;
this._clientIDHash = null;
Services.prefs.setStringPref(PREF_CACHED_CLIENTID, this._clientID);
return true;
},