зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
7ba0490aba
Коммит
dfbb130d83
|
@ -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;
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче