UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

438 lines 21.7 kB
// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../core/common/common.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as SDK from '../../core/sdk/sdk.js'; import { Issue, IssueCategory, IssueKind } from './Issue.js'; import { resolveLazyDescription, } from './MarkdownIssueDescription.js'; const UIStrings = { /** *@description Label for the link for SameSiteCookies Issues */ samesiteCookiesExplained: 'SameSite cookies explained', /** *@description Label for the link for Schemeful Same-Site Issues */ howSchemefulSamesiteWorks: 'How Schemeful Same-Site Works', /** *@description Phrase used to describe the security of a context. Substitued like 'a secure context' or 'a secure origin'. */ aSecure: 'a secure', /** * @description Phrase used to describe the security of a context. Substitued like 'an insecure context' or 'an insecure origin'. */ anInsecure: 'an insecure', /** * @description Label for a link for SameParty Issues. 'Attribute' refers to a cookie attribute. */ firstPartySetsExplained: '`First-Party Sets` and the `SameParty` attribute', /** * @description Label for a link for third-party cookie Issues. */ thirdPartyPhaseoutExplained: 'Prepare for phasing out third-party cookies', }; const str_ = i18n.i18n.registerUIStrings('models/issues_manager/CookieIssue.ts', UIStrings); const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_); export class CookieIssue extends Issue { #issueDetails; constructor(code, issueDetails, issuesModel) { super(code, issuesModel); this.#issueDetails = issueDetails; } #cookieId() { if (this.#issueDetails.cookie) { const { domain, path, name } = this.#issueDetails.cookie; const cookieId = `${domain};${path};${name}`; return cookieId; } return this.#issueDetails.rawCookieLine ?? 'no-cookie-info'; } primaryKey() { const requestId = this.#issueDetails.request ? this.#issueDetails.request.requestId : 'no-request'; return `${this.code()}-(${this.#cookieId()})-(${requestId})`; } /** * Returns an array of issues from a given CookieIssueDetails. */ static createIssuesFromCookieIssueDetails(cookieIssueDetails, issuesModel) { const issues = []; // Exclusion reasons have priority. It means a cookie was blocked. Create an issue // for every exclusion reason but ignore warning reasons if the cookie was blocked. // Some exclusion reasons are dependent on warning reasons existing in order to produce an issue. if (cookieIssueDetails.cookieExclusionReasons && cookieIssueDetails.cookieExclusionReasons.length > 0) { for (const exclusionReason of cookieIssueDetails.cookieExclusionReasons) { const code = CookieIssue.codeForCookieIssueDetails(exclusionReason, cookieIssueDetails.cookieWarningReasons, cookieIssueDetails.operation, cookieIssueDetails.cookieUrl); if (code) { issues.push(new CookieIssue(code, cookieIssueDetails, issuesModel)); } } return issues; } if (cookieIssueDetails.cookieWarningReasons) { for (const warningReason of cookieIssueDetails.cookieWarningReasons) { // warningReasons should be an empty array here. const code = CookieIssue.codeForCookieIssueDetails(warningReason, [], cookieIssueDetails.operation, cookieIssueDetails.cookieUrl); if (code) { issues.push(new CookieIssue(code, cookieIssueDetails, issuesModel)); } } } return issues; } /** * Calculates an issue code from a reason, an operation, and an array of warningReasons. All these together * can uniquely identify a specific cookie issue. * warningReasons is only needed for some CookieExclusionReason in order to determine if an issue should be raised. * It is not required if reason is a CookieWarningReason. */ static codeForCookieIssueDetails(reason, warningReasons, operation, cookieUrl) { const isURLSecure = cookieUrl && (cookieUrl.startsWith('https://') || cookieUrl.startsWith('wss://')); const secure = isURLSecure ? 'Secure' : 'Insecure'; if (reason === "ExcludeSameSiteStrict" /* Protocol.Audits.CookieExclusionReason.ExcludeSameSiteStrict */ || reason === "ExcludeSameSiteLax" /* Protocol.Audits.CookieExclusionReason.ExcludeSameSiteLax */ || reason === "ExcludeSameSiteUnspecifiedTreatedAsLax" /* Protocol.Audits.CookieExclusionReason.ExcludeSameSiteUnspecifiedTreatedAsLax */) { if (warningReasons && warningReasons.length > 0) { if (warningReasons.includes("WarnSameSiteStrictLaxDowngradeStrict" /* Protocol.Audits.CookieWarningReason.WarnSameSiteStrictLaxDowngradeStrict */)) { return [ "CookieIssue" /* Protocol.Audits.InspectorIssueCode.CookieIssue */, 'ExcludeNavigationContextDowngrade', secure, ].join('::'); } if (warningReasons.includes("WarnSameSiteStrictCrossDowngradeStrict" /* Protocol.Audits.CookieWarningReason.WarnSameSiteStrictCrossDowngradeStrict */) || warningReasons.includes("WarnSameSiteStrictCrossDowngradeLax" /* Protocol.Audits.CookieWarningReason.WarnSameSiteStrictCrossDowngradeLax */) || warningReasons.includes("WarnSameSiteLaxCrossDowngradeStrict" /* Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeStrict */) || warningReasons.includes("WarnSameSiteLaxCrossDowngradeLax" /* Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeLax */)) { return [ "CookieIssue" /* Protocol.Audits.InspectorIssueCode.CookieIssue */, 'ExcludeContextDowngrade', operation, secure, ].join('::'); } } // If we have ExcludeSameSiteUnspecifiedTreatedAsLax but no corresponding warnings, then add just // the Issue code for ExcludeSameSiteUnspecifiedTreatedAsLax. if (reason === "ExcludeSameSiteUnspecifiedTreatedAsLax" /* Protocol.Audits.CookieExclusionReason.ExcludeSameSiteUnspecifiedTreatedAsLax */) { return ["CookieIssue" /* Protocol.Audits.InspectorIssueCode.CookieIssue */, reason, operation].join('::'); } // ExcludeSameSiteStrict and ExcludeSameSiteLax require being paired with an appropriate warning. We didn't // find one of those warnings so return null to indicate there shouldn't be an issue created. return null; } if (reason === "WarnSameSiteStrictLaxDowngradeStrict" /* Protocol.Audits.CookieWarningReason.WarnSameSiteStrictLaxDowngradeStrict */) { return ["CookieIssue" /* Protocol.Audits.InspectorIssueCode.CookieIssue */, reason, secure].join('::'); } // These have the same message. if (reason === "WarnSameSiteStrictCrossDowngradeStrict" /* Protocol.Audits.CookieWarningReason.WarnSameSiteStrictCrossDowngradeStrict */ || reason === "WarnSameSiteStrictCrossDowngradeLax" /* Protocol.Audits.CookieWarningReason.WarnSameSiteStrictCrossDowngradeLax */ || reason === "WarnSameSiteLaxCrossDowngradeLax" /* Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeLax */ || reason === "WarnSameSiteLaxCrossDowngradeStrict" /* Protocol.Audits.CookieWarningReason.WarnSameSiteLaxCrossDowngradeStrict */) { return ["CookieIssue" /* Protocol.Audits.InspectorIssueCode.CookieIssue */, 'WarnCrossDowngrade', operation, secure].join('::'); } return ["CookieIssue" /* Protocol.Audits.InspectorIssueCode.CookieIssue */, reason, operation].join('::'); } cookies() { if (this.#issueDetails.cookie) { return [this.#issueDetails.cookie]; } return []; } rawCookieLines() { if (this.#issueDetails.rawCookieLine) { return [this.#issueDetails.rawCookieLine]; } return []; } requests() { if (this.#issueDetails.request) { return [this.#issueDetails.request]; } return []; } getCategory() { return IssueCategory.Cookie; } getDescription() { const description = issueDescriptions.get(this.code()); if (!description) { return null; } return resolveLazyDescription(description); } isCausedByThirdParty() { const outermostFrame = SDK.FrameManager.FrameManager.instance().getOutermostFrame(); return isCausedByThirdParty(outermostFrame, this.#issueDetails.cookieUrl); } getKind() { if (this.#issueDetails.cookieExclusionReasons?.length > 0) { return IssueKind.PageError; } return IssueKind.BreakingChange; } static fromInspectorIssue(issuesModel, inspectorIssue) { const cookieIssueDetails = inspectorIssue.details.cookieIssueDetails; if (!cookieIssueDetails) { console.warn('Cookie issue without details received.'); return []; } return CookieIssue.createIssuesFromCookieIssueDetails(cookieIssueDetails, issuesModel); } } /** * Exported for unit test. */ export function isCausedByThirdParty(outermostFrame, cookieUrl) { if (!outermostFrame) { // The outermost frame is not yet available. Consider this issue as a third-party issue // until the outermost frame is available. This will prevent the issue from being visible // for only just a split second. return true; } // In the case of no domain and registry, we assume its an IP address or localhost // during development, in this case we classify the issue as first-party. if (!cookieUrl || outermostFrame.domainAndRegistry() === '') { return false; } const parsedCookieUrl = Common.ParsedURL.ParsedURL.fromString(cookieUrl); if (!parsedCookieUrl) { return false; } // For both operation types we compare the cookieUrl's domain with the outermost frames // registered domain to determine first-party vs third-party. If they don't match // then we consider this issue a third-party issue. // // For a Set operation: The Set-Cookie response is part of a request to a third-party. // // For a Read operation: The cookie was included in a request to a third-party // site. Only cookies that have their domain also set to this third-party // are included in the request. We assume that the cookie was set by the same // third-party at some point, so we treat this as a third-party issue. // // TODO(crbug.com/1080589): Use "First-Party sets" instead of the sites registered domain. return !isSubdomainOf(parsedCookieUrl.domain(), outermostFrame.domainAndRegistry()); } function isSubdomainOf(subdomain, superdomain) { // Subdomain must be identical or have strictly more labels than the // superdomain. if (subdomain.length <= superdomain.length) { return subdomain === superdomain; } // Superdomain must be suffix of subdomain, and the last character not // included in the matching substring must be a dot. if (!subdomain.endsWith(superdomain)) { return false; } const subdomainWithoutSuperdomian = subdomain.substr(0, subdomain.length - superdomain.length); return subdomainWithoutSuperdomian.endsWith('.'); } const sameSiteUnspecifiedWarnRead = { file: 'SameSiteUnspecifiedLaxAllowUnsafeRead.md', links: [ { link: 'https://web.dev/samesite-cookies-explained/', linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained), }, ], }; const sameSiteUnspecifiedWarnSet = { file: 'SameSiteUnspecifiedLaxAllowUnsafeSet.md', links: [ { link: 'https://web.dev/samesite-cookies-explained/', linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained), }, ], }; const sameSiteNoneInsecureErrorRead = { file: 'SameSiteNoneInsecureErrorRead.md', links: [ { link: 'https://web.dev/samesite-cookies-explained/', linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained), }, ], }; const sameSiteNoneInsecureErrorSet = { file: 'SameSiteNoneInsecureErrorSet.md', links: [ { link: 'https://web.dev/samesite-cookies-explained/', linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained), }, ], }; const sameSiteNoneInsecureWarnRead = { file: 'SameSiteNoneInsecureWarnRead.md', links: [ { link: 'https://web.dev/samesite-cookies-explained/', linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained), }, ], }; const sameSiteNoneInsecureWarnSet = { file: 'SameSiteNoneInsecureWarnSet.md', links: [ { link: 'https://web.dev/samesite-cookies-explained/', linkTitle: i18nLazyString(UIStrings.samesiteCookiesExplained), }, ], }; const schemefulSameSiteArticles = [{ link: 'https://web.dev/schemeful-samesite/', linkTitle: i18nLazyString(UIStrings.howSchemefulSamesiteWorks) }]; function schemefulSameSiteSubstitutions({ isDestinationSecure, isOriginSecure }) { return new Map([ // TODO(crbug.com/1168438): Use translated phrases once the issue description is localized. ['PLACEHOLDER_destination', () => isDestinationSecure ? 'a secure' : 'an insecure'], ['PLACEHOLDER_origin', () => isOriginSecure ? 'a secure' : 'an insecure'], ]); } function sameSiteWarnStrictLaxDowngradeStrict(isSecure) { return { file: 'SameSiteWarnStrictLaxDowngradeStrict.md', substitutions: schemefulSameSiteSubstitutions({ isDestinationSecure: isSecure, isOriginSecure: !isSecure }), links: schemefulSameSiteArticles, }; } function sameSiteExcludeNavigationContextDowngrade(isSecure) { return { file: 'SameSiteExcludeNavigationContextDowngrade.md', substitutions: schemefulSameSiteSubstitutions({ isDestinationSecure: isSecure, isOriginSecure: !isSecure }), links: schemefulSameSiteArticles, }; } function sameSiteWarnCrossDowngradeRead(isSecure) { return { file: 'SameSiteWarnCrossDowngradeRead.md', substitutions: schemefulSameSiteSubstitutions({ isDestinationSecure: isSecure, isOriginSecure: !isSecure }), links: schemefulSameSiteArticles, }; } function sameSiteExcludeContextDowngradeRead(isSecure) { return { file: 'SameSiteExcludeContextDowngradeRead.md', substitutions: schemefulSameSiteSubstitutions({ isDestinationSecure: isSecure, isOriginSecure: !isSecure }), links: schemefulSameSiteArticles, }; } function sameSiteWarnCrossDowngradeSet(isSecure) { return { file: 'SameSiteWarnCrossDowngradeSet.md', substitutions: schemefulSameSiteSubstitutions({ isDestinationSecure: !isSecure, isOriginSecure: isSecure }), links: schemefulSameSiteArticles, }; } function sameSiteExcludeContextDowngradeSet(isSecure) { return { file: 'SameSiteExcludeContextDowngradeSet.md', substitutions: schemefulSameSiteSubstitutions({ isDestinationSecure: isSecure, isOriginSecure: !isSecure }), links: schemefulSameSiteArticles, }; } const sameSiteInvalidSameParty = { file: 'SameSiteInvalidSameParty.md', links: [{ link: 'https://developer.chrome.com/blog/first-party-sets-sameparty/', linkTitle: i18nLazyString(UIStrings.firstPartySetsExplained), }], }; const samePartyCrossPartyContextSet = { file: 'SameSiteSamePartyCrossPartyContextSet.md', links: [{ link: 'https://developer.chrome.com/blog/first-party-sets-sameparty/', linkTitle: i18nLazyString(UIStrings.firstPartySetsExplained), }], }; const attributeValueExceedsMaxSize = { file: 'CookieAttributeValueExceedsMaxSize.md', links: [], }; const warnDomainNonAscii = { file: 'cookieWarnDomainNonAscii.md', links: [], }; const excludeDomainNonAscii = { file: 'cookieExcludeDomainNonAscii.md', links: [], }; const excludeBlockedWithinFirstPartySet = { file: 'cookieExcludeBlockedWithinFirstPartySet.md', links: [], }; const cookieWarnThirdPartyPhaseoutSet = { file: 'cookieWarnThirdPartyPhaseoutSet.md', links: [{ link: 'https://developer.chrome.com/docs/privacy-sandbox/third-party-cookie-phase-out/', linkTitle: i18nLazyString(UIStrings.thirdPartyPhaseoutExplained), }], }; const cookieWarnThirdPartyPhaseoutRead = { file: 'cookieWarnThirdPartyPhaseoutRead.md', links: [{ link: 'https://developer.chrome.com/docs/privacy-sandbox/third-party-cookie-phase-out/', linkTitle: i18nLazyString(UIStrings.thirdPartyPhaseoutExplained), }], }; const cookieExcludeThirdPartyPhaseoutSet = { file: 'cookieExcludeThirdPartyPhaseoutSet.md', links: [{ link: 'https://developer.chrome.com/docs/privacy-sandbox/third-party-cookie-phase-out/', linkTitle: i18nLazyString(UIStrings.thirdPartyPhaseoutExplained), }], }; const cookieExcludeThirdPartyPhaseoutRead = { file: 'cookieExcludeThirdPartyPhaseoutRead.md', links: [{ link: 'https://developer.chrome.com/docs/privacy-sandbox/third-party-cookie-phase-out/', linkTitle: i18nLazyString(UIStrings.thirdPartyPhaseoutExplained), }], }; const issueDescriptions = new Map([ // These two don't have a deprecation date yet, but they need to be fixed eventually. ['CookieIssue::WarnSameSiteUnspecifiedLaxAllowUnsafe::ReadCookie', sameSiteUnspecifiedWarnRead], ['CookieIssue::WarnSameSiteUnspecifiedLaxAllowUnsafe::SetCookie', sameSiteUnspecifiedWarnSet], ['CookieIssue::WarnSameSiteUnspecifiedCrossSiteContext::ReadCookie', sameSiteUnspecifiedWarnRead], ['CookieIssue::WarnSameSiteUnspecifiedCrossSiteContext::SetCookie', sameSiteUnspecifiedWarnSet], ['CookieIssue::ExcludeSameSiteNoneInsecure::ReadCookie', sameSiteNoneInsecureErrorRead], ['CookieIssue::ExcludeSameSiteNoneInsecure::SetCookie', sameSiteNoneInsecureErrorSet], ['CookieIssue::WarnSameSiteNoneInsecure::ReadCookie', sameSiteNoneInsecureWarnRead], ['CookieIssue::WarnSameSiteNoneInsecure::SetCookie', sameSiteNoneInsecureWarnSet], ['CookieIssue::WarnSameSiteStrictLaxDowngradeStrict::Secure', sameSiteWarnStrictLaxDowngradeStrict(true)], ['CookieIssue::WarnSameSiteStrictLaxDowngradeStrict::Insecure', sameSiteWarnStrictLaxDowngradeStrict(false)], ['CookieIssue::WarnCrossDowngrade::ReadCookie::Secure', sameSiteWarnCrossDowngradeRead(true)], ['CookieIssue::WarnCrossDowngrade::ReadCookie::Insecure', sameSiteWarnCrossDowngradeRead(false)], ['CookieIssue::WarnCrossDowngrade::SetCookie::Secure', sameSiteWarnCrossDowngradeSet(true)], ['CookieIssue::WarnCrossDowngrade::SetCookie::Insecure', sameSiteWarnCrossDowngradeSet(false)], ['CookieIssue::ExcludeNavigationContextDowngrade::Secure', sameSiteExcludeNavigationContextDowngrade(true)], [ 'CookieIssue::ExcludeNavigationContextDowngrade::Insecure', sameSiteExcludeNavigationContextDowngrade(false), ], ['CookieIssue::ExcludeContextDowngrade::ReadCookie::Secure', sameSiteExcludeContextDowngradeRead(true)], ['CookieIssue::ExcludeContextDowngrade::ReadCookie::Insecure', sameSiteExcludeContextDowngradeRead(false)], ['CookieIssue::ExcludeContextDowngrade::SetCookie::Secure', sameSiteExcludeContextDowngradeSet(true)], ['CookieIssue::ExcludeContextDowngrade::SetCookie::Insecure', sameSiteExcludeContextDowngradeSet(false)], ['CookieIssue::ExcludeInvalidSameParty::SetCookie', sameSiteInvalidSameParty], ['CookieIssue::ExcludeSamePartyCrossPartyContext::SetCookie', samePartyCrossPartyContextSet], ['CookieIssue::WarnAttributeValueExceedsMaxSize::ReadCookie', attributeValueExceedsMaxSize], ['CookieIssue::WarnAttributeValueExceedsMaxSize::SetCookie', attributeValueExceedsMaxSize], ['CookieIssue::WarnDomainNonASCII::ReadCookie', warnDomainNonAscii], ['CookieIssue::WarnDomainNonASCII::SetCookie', warnDomainNonAscii], ['CookieIssue::ExcludeDomainNonASCII::ReadCookie', excludeDomainNonAscii], ['CookieIssue::ExcludeDomainNonASCII::SetCookie', excludeDomainNonAscii], [ 'CookieIssue::ExcludeThirdPartyCookieBlockedInFirstPartySet::ReadCookie', excludeBlockedWithinFirstPartySet, ], [ 'CookieIssue::ExcludeThirdPartyCookieBlockedInFirstPartySet::SetCookie', excludeBlockedWithinFirstPartySet, ], ['CookieIssue::WarnThirdPartyPhaseout::ReadCookie', cookieWarnThirdPartyPhaseoutRead], ['CookieIssue::WarnThirdPartyPhaseout::SetCookie', cookieWarnThirdPartyPhaseoutSet], ['CookieIssue::ExcludeThirdPartyPhaseout::ReadCookie', cookieExcludeThirdPartyPhaseoutRead], ['CookieIssue::ExcludeThirdPartyPhaseout::SetCookie', cookieExcludeThirdPartyPhaseoutSet], ]); //# sourceMappingURL=CookieIssue.js.map