Skip to content

Commit d89a962

Browse files
masnobleDevtools-frontend LUCI CQ
authored andcommitted
Retrieve and show cookie report data
As relevant cookieIssues are reported by the issueManager, the CookieReportView is notified and will gather the cookieReportInfo from the cookieIssue. This report information is pulled from the third-party-web. https://screenshot.googleplex.com/9n4NAQnhu69niv2.png Bug: 365737493 Change-Id: I86967ce6d76c4aa14c4c3de2192dc7638e7f5b2d Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5980008 Commit-Queue: Joshua Thomas <[email protected]> Reviewed-by: Danil Somsikov <[email protected]> Reviewed-by: Shuran Huang <[email protected]>
1 parent 315ec00 commit d89a962

File tree

11 files changed

+377
-45
lines changed

11 files changed

+377
-45
lines changed

front_end/models/issues_manager/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ devtools_module("issues_manager") {
5353
"../../models/text_utils:bundle",
5454
"../../models/workspace:bundle",
5555
"../../third_party/marked:bundle",
56+
"../../third_party/third-party-web:bundle",
5657
]
5758

5859
public_deps = [ ":descriptions" ]
@@ -231,6 +232,7 @@ devtools_entrypoint("bundle") {
231232
"../../panels/elements/*",
232233
"../../panels/issues/*",
233234
"../../panels/network/*",
235+
"../../panels/security/*",
234236
"../../panels/sources/*",
235237
"../../testing/*",
236238
"../../ui/components/docs/issue_counter/*",

front_end/models/issues_manager/CookieIssue.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as i18n from '../../core/i18n/i18n.js';
88
import type * as Platform from '../../core/platform/platform.js';
99
import * as SDK from '../../core/sdk/sdk.js';
1010
import * as Protocol from '../../generated/protocol.js';
11+
import * as ThirdPartyWeb from '../../third_party/third-party-web/third-party-web.js';
1112

1213
import {Issue, IssueCategory, IssueKind} from './Issue.js';
1314
import {
@@ -67,6 +68,23 @@ export const enum CookieIssueSubCategory {
6768
THIRD_PARTY_PHASEOUT_COOKIE = 'ThirdPartyPhaseoutCookie',
6869
}
6970

71+
// Enum to show cookie status from the security panel's third-party cookie report tool
72+
export const enum CookieStatus {
73+
BLOCKED = 0,
74+
ALLOWED = 1,
75+
ALLOWED_BY_GRACE_PERIOD = 2,
76+
ALLOWED_BY_HEURISTICS = 3,
77+
}
78+
79+
export interface CookieReportInfo {
80+
name: string;
81+
domain: string;
82+
type?: string;
83+
platform?: string;
84+
status: CookieStatus;
85+
recommendation?: string;
86+
}
87+
7088
export class CookieIssue extends Issue {
7189
#issueDetails: Protocol.Audits.CookieIssueDetails;
7290

@@ -77,7 +95,7 @@ export class CookieIssue extends Issue {
7795
this.#issueDetails = issueDetails;
7896
}
7997

80-
#cookieId(): string {
98+
cookieId(): string {
8199
if (this.#issueDetails.cookie) {
82100
const {domain, path, name} = this.#issueDetails.cookie;
83101
const cookieId = `${domain};${path};${name}`;
@@ -88,7 +106,7 @@ export class CookieIssue extends Issue {
88106

89107
primaryKey(): string {
90108
const requestId = this.#issueDetails.request ? this.#issueDetails.request.requestId : 'no-request';
91-
return `${this.code()}-(${this.#cookieId()})-(${requestId})`;
109+
return `${this.code()}-(${this.cookieId()})-(${requestId})`;
92110
}
93111

94112
/**
@@ -253,6 +271,45 @@ export class CookieIssue extends Issue {
253271
return IssueKind.BREAKING_CHANGE;
254272
}
255273

274+
makeCookieReportEntry(): CookieReportInfo|undefined {
275+
const status = CookieIssue.getCookieStatus(this.#issueDetails);
276+
if (this.#issueDetails.cookie && this.#issueDetails.cookieUrl && status !== undefined) {
277+
const entity = ThirdPartyWeb.ThirdPartyWeb.getEntity(this.#issueDetails.cookieUrl);
278+
return {
279+
name: this.#issueDetails.cookie.name,
280+
domain: this.#issueDetails.cookie.domain,
281+
type: entity?.category,
282+
platform: entity?.name,
283+
status,
284+
};
285+
}
286+
287+
return;
288+
}
289+
290+
static getCookieStatus(cookieIssueDetails: Protocol.Audits.CookieIssueDetails): CookieStatus|undefined {
291+
if (cookieIssueDetails.cookieExclusionReasons.includes(
292+
Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout)) {
293+
return CookieStatus.BLOCKED;
294+
}
295+
296+
if (cookieIssueDetails.cookieWarningReasons.includes(
297+
Protocol.Audits.CookieWarningReason.WarnDeprecationTrialMetadata)) {
298+
return CookieStatus.ALLOWED_BY_GRACE_PERIOD;
299+
}
300+
301+
if (cookieIssueDetails.cookieWarningReasons.includes(
302+
Protocol.Audits.CookieWarningReason.WarnThirdPartyCookieHeuristic)) {
303+
return CookieStatus.ALLOWED_BY_HEURISTICS;
304+
}
305+
306+
if (cookieIssueDetails.cookieWarningReasons.includes(Protocol.Audits.CookieWarningReason.WarnThirdPartyPhaseout)) {
307+
return CookieStatus.ALLOWED;
308+
}
309+
310+
return;
311+
}
312+
256313
static fromInspectorIssue(issuesModel: SDK.IssuesModel.IssuesModel, inspectorIssue: Protocol.Audits.InspectorIssue):
257314
CookieIssue[] {
258315
const cookieIssueDetails = inspectorIssue.details.cookieIssueDetails;

front_end/panels/security/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ devtools_module("security") {
3535
"../../core/i18n:bundle",
3636
"../../core/sdk:bundle",
3737
"../../generated:protocol",
38+
"../../models/issues_manager:bundle",
3839
"../../panels/network/forward:bundle",
3940
"../../ui/legacy:bundle",
41+
"../../ui/legacy/components/data_grid:bundle",
4042
]
4143
}
4244

@@ -72,6 +74,7 @@ ts_library("unittests") {
7274
testonly = true
7375

7476
sources = [
77+
"CookieReportView.test.ts",
7578
"SecurityModel.test.ts",
7679
"SecurityPanel.test.ts",
7780
]
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as Protocol from '../../generated/protocol.js';
6+
import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
7+
import {createFakeSetting, createTarget} from '../../testing/EnvironmentHelpers.js';
8+
import {describeWithMockConnection} from '../../testing/MockConnection.js';
9+
10+
import * as Security from './security.js';
11+
12+
function getTestCookieIssue(
13+
readCookie?: boolean, exclusionReason?: Protocol.Audits.CookieExclusionReason,
14+
warningReason?: Protocol.Audits.CookieWarningReason, cookieName?: string): Protocol.Audits.InspectorIssue {
15+
// if no exclusion or warning reason provided, use a default
16+
if (!exclusionReason && !warningReason) {
17+
exclusionReason = Protocol.Audits.CookieExclusionReason.ExcludeThirdPartyPhaseout;
18+
}
19+
20+
return {
21+
code: Protocol.Audits.InspectorIssueCode.CookieIssue,
22+
details: {
23+
cookieIssueDetails: {
24+
cookie: {
25+
name: cookieName + 'test',
26+
path: '/',
27+
domain: 'a.' + cookieName + 'test',
28+
},
29+
cookieExclusionReasons: exclusionReason ? [exclusionReason] : [],
30+
cookieWarningReasons: warningReason ? [warningReason] : [],
31+
operation: readCookie ? Protocol.Audits.CookieOperation.ReadCookie : Protocol.Audits.CookieOperation.SetCookie,
32+
cookieUrl: 'a.' + cookieName + 'test',
33+
},
34+
},
35+
};
36+
}
37+
38+
describeWithMockConnection('CookieReportView', () => {
39+
let mockView: sinon.SinonStub;
40+
41+
beforeEach(() => {
42+
mockView = sinon.stub();
43+
createTarget();
44+
const showThirdPartyIssuesSetting = createFakeSetting('third party flag', true);
45+
IssuesManager.IssuesManager.IssuesManager.instance({
46+
forceNew: false,
47+
ensureFirst: false,
48+
showThirdPartyIssuesSetting,
49+
});
50+
});
51+
52+
it('should contain no rows if no issues were created', async () => {
53+
const view = new Security.CookieReportView.CookieReportView(undefined, mockView);
54+
55+
assert.strictEqual(view.gridData.length, 0);
56+
});
57+
58+
it('should have row when there was a preexisting cookie issue', async () => {
59+
// @ts-ignore
60+
globalThis.addIssueForTest(getTestCookieIssue());
61+
62+
const view = new Security.CookieReportView.CookieReportView(undefined, mockView);
63+
await view.pendingUpdate();
64+
65+
assert.strictEqual(view.gridData.length, 1);
66+
});
67+
68+
it('should add row when issue added after view creation', async () => {
69+
const view = new Security.CookieReportView.CookieReportView(undefined, mockView);
70+
71+
await view.pendingUpdate();
72+
assert.strictEqual(view.gridData.length, 0);
73+
74+
// @ts-ignore
75+
globalThis.addIssueForTest(getTestCookieIssue());
76+
77+
await view.pendingUpdate();
78+
assert.strictEqual(view.gridData.length, 1);
79+
});
80+
81+
it('should ignore non-third-party-cookie related exclusionReason', async () => {
82+
const view = new Security.CookieReportView.CookieReportView(undefined, mockView);
83+
84+
// @ts-ignore
85+
globalThis.addIssueForTest(
86+
getTestCookieIssue(undefined, Protocol.Audits.CookieExclusionReason.ExcludeSameSiteNoneInsecure));
87+
88+
await view.pendingUpdate();
89+
assert.strictEqual(view.gridData.length, 0);
90+
91+
// Make sure ExcludeThirdPartyPhaseout (default) is added.
92+
// @ts-ignore
93+
globalThis.addIssueForTest(getTestCookieIssue());
94+
95+
await view.pendingUpdate();
96+
assert.strictEqual(view.gridData.length, 1);
97+
assert.strictEqual(view.gridData[0].data.status, 'Blocked');
98+
});
99+
100+
it('should ignore non-third-party-cookie related warningReason', async () => {
101+
const view = new Security.CookieReportView.CookieReportView(undefined, mockView);
102+
103+
// @ts-ignore
104+
globalThis.addIssueForTest(
105+
getTestCookieIssue(undefined, undefined, Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeLax));
106+
107+
await view.pendingUpdate();
108+
assert.strictEqual(view.gridData.length, 0);
109+
110+
// Make sure warning 3pc warning reasons are added
111+
// @ts-ignore
112+
globalThis.addIssueForTest(getTestCookieIssue(
113+
undefined, undefined, Protocol.Audits.CookieWarningReason.WarnDeprecationTrialMetadata, 'metadata'));
114+
// @ts-ignore
115+
globalThis.addIssueForTest(getTestCookieIssue(
116+
undefined, undefined, Protocol.Audits.CookieWarningReason.WarnThirdPartyCookieHeuristic, 'heuristic'));
117+
// @ts-ignore
118+
globalThis.addIssueForTest(getTestCookieIssue(
119+
undefined, undefined, Protocol.Audits.CookieWarningReason.WarnThirdPartyPhaseout, 'phaseout'));
120+
121+
await view.pendingUpdate();
122+
assert.strictEqual(view.gridData.length, 3);
123+
assert.strictEqual(view.gridData[0].data.status, 'Allowed By Exception');
124+
assert.strictEqual(view.gridData[1].data.status, 'Allowed By Exception');
125+
assert.strictEqual(view.gridData[2].data.status, 'Allowed');
126+
});
127+
128+
it('should only have a single entry for same cookie with a read and a write operations', async () => {
129+
const view = new Security.CookieReportView.CookieReportView(undefined, mockView);
130+
131+
// @ts-ignore
132+
globalThis.addIssueForTest(getTestCookieIssue(true));
133+
// @ts-ignore
134+
globalThis.addIssueForTest(getTestCookieIssue(false));
135+
136+
await view.pendingUpdate();
137+
assert.strictEqual(view.gridData.length, 1);
138+
});
139+
});

0 commit comments

Comments
 (0)