Skip to content

Commit de6a7d0

Browse files
Merge pull request #307 from mozilla/browsed-referrer
Blocked domain telemetry: Add referrer and original URL for redirects
2 parents bd0843c + d23460e commit de6a7d0

File tree

5 files changed

+270
-10
lines changed

5 files changed

+270
-10
lines changed

browser/components/enterprisepolicies/helpers/WebsiteFilter.sys.mjs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,6 @@ export let WebsiteFilter = {
124124
contentType == Ci.nsIContentPolicy.TYPE_SUBDOCUMENT
125125
) {
126126
if (!this.isAllowed(url)) {
127-
#ifdef MOZ_ENTERPRISE
128-
this._recordBlocklistDomainBrowsed(url);
129-
#endif
130127
return Ci.nsIContentPolicy.REJECT_POLICY;
131128
}
132129
}
@@ -152,6 +149,19 @@ export let WebsiteFilter = {
152149
url = URL.parse(location, channel.URI.spec);
153150
}
154151
if (url && !this.isAllowed(url.href)) {
152+
#ifdef MOZ_ENTERPRISE
153+
let referrerSpec = "";
154+
try {
155+
let referrerInfo = channel.referrerInfo;
156+
if (referrerInfo) {
157+
let originalReferrer = referrerInfo.originalReferrer;
158+
if (originalReferrer) {
159+
referrerSpec = originalReferrer.spec;
160+
}
161+
}
162+
} catch (e) {}
163+
this._recordBlocklistDomainBrowsed(channel.originalURI.spec, url.href, referrerSpec);
164+
#endif
155165
channel.cancel(Cr.NS_ERROR_BLOCKED_BY_POLICY);
156166
}
157167
} catch (e) {}
@@ -180,7 +190,7 @@ export let WebsiteFilter = {
180190
},
181191
/* eslint-disable */
182192
#ifdef MOZ_ENTERPRISE
183-
_recordBlocklistDomainBrowsed(url) {
193+
_recordBlocklistDomainBrowsed(originalUrl, resolvedUrl, referrer) {
184194
const isEnabled = Services.prefs.getBoolPref(
185195
"browser.policies.enterprise.telemetry.blocklistDomainBrowsed.enabled",
186196
true
@@ -190,11 +200,23 @@ export let WebsiteFilter = {
190200
}
191201

192202
try {
193-
const processedUrl = this._processTelemetryUrl(url);
194-
Glean.contentPolicy.blocklistDomainBrowsed.record({
195-
url: processedUrl,
196-
});
197-
GleanPings.enterprise.submit();
203+
const processedOrigUrl = this._processTelemetryUrl(originalUrl);
204+
const processedResolvedUrl = this._processTelemetryUrl(resolvedUrl);
205+
const processedReferrer = this._processTelemetryUrl(referrer);
206+
const telemetryData = {
207+
original_url: processedOrigUrl || "",
208+
url: processedResolvedUrl || "",
209+
referrer: processedReferrer || "",
210+
};
211+
Glean.contentPolicy.blocklistDomainBrowsed.record(telemetryData);
212+
if (
213+
!Services.prefs.getBoolPref(
214+
"browser.policies.enterprise.telemetry.testing.disableSubmit",
215+
false
216+
)
217+
) {
218+
GleanPings.enterprise.submit();
219+
}
198220
} catch (ex) {
199221
// Silently fail - telemetry errors should not break website filtering
200222
console.error(

browser/components/enterprisepolicies/metrics.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ content_policy:
3232
extra_keys:
3333
url:
3434
description: >
35-
The blocked url that was browsed.
35+
The url that was blocked by policy.
36+
type: string
37+
original_url:
38+
description: >
39+
The original url, prior to redirects, that was requested resulting in a block,
40+
if different from the blocked url.
41+
type: string
42+
referrer:
43+
description: >
44+
The referrer address from which the url was requested.
3645
type: string
3746
#endif

browser/components/enterprisepolicies/tests/browser/browser.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,10 @@ skip-if = [
135135
["browser_policy_usermessaging.js"]
136136

137137
["browser_policy_websitefilter.js"]
138+
139+
["browser_policy_websitefilter_telemetry.js"]
140+
# Only run in MOZ_ENTERPRISE builds
141+
skip-if = [
142+
"!buildapp:firefox",
143+
"!enterprise",
144+
]
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/* Any copyright is dedicated to the Public Domain.
2+
* http://creativecommons.org/publicdomain/zero/1.0/ */
3+
"use strict";
4+
5+
const SUPPORT_FILES_PATH =
6+
"http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/";
7+
const BLOCKED_PAGE = "policy_websitefilter_block.html";
8+
const SAVELINKAS_PAGE = "policy_websitefilter_savelink.html";
9+
10+
async function clearWebsiteFilter() {
11+
await setupPolicyEngineWithJson({
12+
policies: {
13+
WebsiteFilter: {
14+
Block: [],
15+
Exceptions: [],
16+
},
17+
},
18+
});
19+
}
20+
21+
add_task(async function test_policy_enterprise_telemetry() {
22+
await setupPolicyEngineWithJson({
23+
policies: {
24+
WebsiteFilter: `{
25+
"Block": ["*://mochi.test/*policy_websitefilter_block*"]
26+
}`,
27+
},
28+
});
29+
await SpecialPowers.pushPrefEnv({
30+
set: [
31+
["browser.policies.enterprise.telemetry.testing.disableSubmit", true],
32+
[
33+
"browser.policies.enterprise.telemetry.blocklistDomainBrowsed.enabled",
34+
true,
35+
],
36+
[
37+
"browser.policies.enterprise.telemetry.blocklistDomainBrowsed.urlLogging",
38+
"full",
39+
],
40+
],
41+
});
42+
43+
const referrerURL = SUPPORT_FILES_PATH + SAVELINKAS_PAGE;
44+
const resolvedURL = SUPPORT_FILES_PATH + BLOCKED_PAGE;
45+
await checkBlockedPageTelemetry(SUPPORT_FILES_PATH + BLOCKED_PAGE);
46+
await checkBlockedPageTelemetry(SUPPORT_FILES_PATH + BLOCKED_PAGE, {
47+
referrerURL,
48+
});
49+
await checkBlockedPageTelemetry(
50+
"view-source:" + SUPPORT_FILES_PATH + BLOCKED_PAGE
51+
);
52+
await checkBlockedPageTelemetry(
53+
"about:reader?url=" + SUPPORT_FILES_PATH + BLOCKED_PAGE
54+
);
55+
56+
await checkBlockedPageTelemetry(SUPPORT_FILES_PATH + "301.sjs", {
57+
resolvedURL,
58+
});
59+
await checkBlockedPageTelemetry(SUPPORT_FILES_PATH + "301.sjs", {
60+
resolvedURL,
61+
referrerURL,
62+
});
63+
64+
await checkBlockedPageTelemetry(SUPPORT_FILES_PATH + "302.sjs", {
65+
resolvedURL,
66+
});
67+
await checkBlockedPageTelemetry(SUPPORT_FILES_PATH + "302.sjs", {
68+
resolvedURL,
69+
referrerURL,
70+
});
71+
72+
await clearWebsiteFilter();
73+
});
74+
75+
// Checks that a page was blocked by seeing if it was replaced with about:neterror
76+
async function checkBlockedPageTelemetry(
77+
url,
78+
{ resolvedURL, referrerURL } = {}
79+
) {
80+
const expectedBlockedUrl = resolvedURL ?? url;
81+
82+
let newTab;
83+
try {
84+
if (referrerURL) {
85+
newTab = await BrowserTestUtils.openNewForegroundTab(
86+
gBrowser,
87+
referrerURL
88+
);
89+
90+
await SpecialPowers.spawn(newTab.linkedBrowser, [url], async href => {
91+
let link = content.document.getElementById("savelink_blocked");
92+
link.href = href;
93+
});
94+
} else {
95+
newTab = BrowserTestUtils.addTab(gBrowser);
96+
gBrowser.selectedTab = newTab;
97+
}
98+
let browser = newTab.linkedBrowser;
99+
100+
let promise = BrowserTestUtils.waitForErrorPage(browser);
101+
if (referrerURL) {
102+
await BrowserTestUtils.synthesizeMouseAtCenter(
103+
"#savelink_blocked",
104+
{},
105+
browser
106+
);
107+
} else {
108+
BrowserTestUtils.startLoadingURIString(browser, url);
109+
}
110+
await promise;
111+
112+
let events =
113+
Glean.contentPolicy.blocklistDomainBrowsed.testGetValue("enterprise");
114+
Assert.ok(events?.length, "Should have recorded events");
115+
if (!events?.length) {
116+
return;
117+
}
118+
Assert.greaterOrEqual(events.length, 1, "Should record at least one event"); // TODO this should eventually be exactly 1
119+
const event = events.at(-1);
120+
Assert.ok(event.extra, "Event should have extra data");
121+
Assert.equal(
122+
event.extra.url,
123+
expectedBlockedUrl,
124+
"Telemetry should include blocked URL"
125+
);
126+
if (resolvedURL) {
127+
Assert.equal(
128+
event.extra.original_url,
129+
url,
130+
"Telemetry should include original requested URL"
131+
);
132+
}
133+
if (referrerURL) {
134+
Assert.equal(
135+
event.extra.referrer,
136+
referrerURL,
137+
"Telemetry should include referrer URL"
138+
);
139+
}
140+
} finally {
141+
if (newTab) {
142+
BrowserTestUtils.removeTab(newTab);
143+
}
144+
Services.fog.testResetFOG();
145+
}
146+
}

dom/security/nsContentSecurityManager.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
#include "mozilla/dom/nsMixedContentBlocker.h"
2929
#include "mozilla/extensions/WebExtensionPolicy.h"
3030
#include "mozilla/glean/DomSecurityMetrics.h"
31+
#if defined(MOZ_ENTERPRISE)
32+
# include "mozilla/glean/EnterprisepoliciesMetrics.h"
33+
# include "mozilla/glean/GleanPings.h"
34+
#endif
3135
#include "nsAboutProtocolUtils.h"
3236
#include "nsArray.h"
3337
#include "nsCORSListenerProxy.h"
@@ -64,6 +68,75 @@ NS_IMPL_ISUPPORTS(nsContentSecurityManager, nsIContentSecurityManager,
6468
mozilla::LazyLogModule sCSMLog("CSMLog");
6569
mozilla::LazyLogModule sUELLog("UnexpectedLoad");
6670

71+
#if defined(MOZ_ENTERPRISE)
72+
73+
static bool BlocklistDomainBrowsedTelemetryIsEnabled() {
74+
return Preferences::GetBool(
75+
"browser.policies.enterprise.telemetry.blocklistDomainBrowsed.enabled",
76+
true);
77+
}
78+
79+
static nsCString BlocklistDomainBrowsedTelemetryUrlLoggingPolicy() {
80+
nsCString urlLogging;
81+
if (NS_FAILED(Preferences::GetCString("browser.policies.enterprise.telemetry."
82+
"blocklistDomainBrowsed.urlLogging",
83+
urlLogging)) ||
84+
urlLogging.IsEmpty()) {
85+
urlLogging.AssignLiteral("full");
86+
}
87+
return urlLogging;
88+
}
89+
90+
static nsCString ProcessBlocklistDomainBrowsedTelemetryUrl(
91+
nsIURI* aURI, const nsCString& aPolicy) {
92+
if (!aURI || aPolicy.EqualsLiteral("none")) {
93+
return ""_ns;
94+
}
95+
96+
if (aPolicy.EqualsLiteral("domain")) {
97+
nsCString host;
98+
if (NS_FAILED(aURI->GetHost(host))) {
99+
return ""_ns;
100+
}
101+
return host;
102+
}
103+
104+
nsCString spec;
105+
aURI->GetSpec(spec);
106+
return spec;
107+
}
108+
109+
static void RecordBlocklistDomainBrowsedTelemetry(nsIChannel* aChannel,
110+
nsIURI* aURI) {
111+
if (!BlocklistDomainBrowsedTelemetryIsEnabled()) {
112+
return;
113+
}
114+
115+
const nsCString urlLogging =
116+
BlocklistDomainBrowsedTelemetryUrlLoggingPolicy();
117+
118+
const nsCString blockedUrlTelemetry =
119+
ProcessBlocklistDomainBrowsedTelemetryUrl(aURI, urlLogging);
120+
121+
nsCOMPtr<nsIURI> referrer;
122+
NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
123+
const nsCString referrerTelemetry =
124+
ProcessBlocklistDomainBrowsedTelemetryUrl(referrer, urlLogging);
125+
126+
glean::content_policy::BlocklistDomainBrowsedExtra extra = {
127+
.referrer = Some(referrerTelemetry),
128+
.url = Some(blockedUrlTelemetry),
129+
};
130+
glean::content_policy::blocklist_domain_browsed.Record(Some(extra));
131+
132+
if (!Preferences::GetBool(
133+
"browser.policies.enterprise.telemetry.testing.disableSubmit",
134+
false)) {
135+
glean_pings::Enterprise.Submit();
136+
}
137+
}
138+
#endif
139+
67140
// These first two are used for off-the-main-thread checks of
68141
// general.config.filename
69142
// (which can't be checked off-main-thread).
@@ -537,6 +610,9 @@ static nsresult DoContentSecurityChecks(nsIChannel* aChannel,
537610
return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
538611
}
539612
if (shouldLoad == nsIContentPolicy::REJECT_POLICY) {
613+
#if defined(MOZ_ENTERPRISE)
614+
RecordBlocklistDomainBrowsedTelemetry(aChannel, uri);
615+
#endif
540616
return NS_ERROR_BLOCKED_BY_POLICY;
541617
}
542618
if (shouldLoad == nsIContentPolicy::REJECT_RESTARTFORCED) {

0 commit comments

Comments
 (0)