Skip to content

Commit

Permalink
core(is-on-https): add mixed-content resolution (#10975)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark authored Jul 10, 2020
1 parent 74492bc commit 5acd30f
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,12 @@ const expectations = [
'is-on-https': {
score: 0,
details: {
items: {
length: 1,
},
items: [
{
url: 'http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js',
resolution: 'Allowed',
},
],
},
},
'uses-http2': {
Expand Down
56 changes: 49 additions & 7 deletions lighthouse-core/audits/is-on-https.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ const UIStrings = {
}`,
/** Label for a column in a data table; entries in the column will be the URLs of insecure (non-HTTPS) network requests. */
columnInsecureURL: 'Insecure URL',
/** Label for a column in a data table; entries in the column will be how the browser handled insecure (non-HTTPS) network requests. */
columnResolution: 'Request Resolution',
/** Value for the resolution column in a data table; denotes that the insecure URL was allowed by the browser. */
allowed: 'Allowed',
/** Value for the resolution column in a data table; denotes that the insecure URL was blocked by the browser. */
blocked: 'Blocked',
/** Value for the resolution column in a data table; denotes that the insecure URL may be blocked by the browser in the future. */
warning: 'Allowed with warning',
/** Value for the resolution column in a data table; denotes that the insecure URL was upgraded to a secure request by the browser. */
upgraded: 'Automatically upgraded to HTTPS',
};

const resolutionToString = {
MixedContentAutomaticallyUpgraded: UIStrings.upgraded,
MixedContentBlocked: UIStrings.blocked,
MixedContentWarning: UIStrings.warning,
};

const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
Expand All @@ -48,7 +64,7 @@ class HTTPS extends Audit {
title: str_(UIStrings.title),
failureTitle: str_(UIStrings.failureTitle),
description: str_(UIStrings.description),
requiredArtifacts: ['devtoolsLogs'],
requiredArtifacts: ['devtoolsLogs', 'InspectorIssues'],
};
}

Expand All @@ -74,18 +90,44 @@ class HTTPS extends Audit {
.filter(record => !HTTPS.isSecureRecord(record))
.map(record => URL.elideDataURI(record.url));

const items = Array.from(new Set(insecureURLs)).map(url => ({url}));

let displayValue = '';
if (items.length > 0) {
displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
}
/** @type {Array<{url: string, resolution?: string}>} */
const items = Array.from(new Set(insecureURLs)).map(url => ({url, resolution: undefined}));

/** @type {LH.Audit.Details.Table['headings']} */
const headings = [
{key: 'url', itemType: 'url', text: str_(UIStrings.columnInsecureURL)},
];

// Mixed-content issues aren't emitted until M84.
if (artifacts.InspectorIssues.mixedContent.length) {
headings.push(
{key: 'resolution', itemType: 'text', text: str_(UIStrings.columnResolution)});
for (const details of artifacts.InspectorIssues.mixedContent) {
let item = items.find(item => item.url === details.insecureURL);
if (!item) {
item = {url: details.insecureURL};
items.push(item);
}
item.resolution = resolutionToString[details.resolutionStatus] ?
str_(resolutionToString[details.resolutionStatus]) :
details.resolutionStatus;
}
}

// If a resolution wasn't assigned from an InspectorIssue, then the item
// is not blocked by the browser but we've determined it is insecure anyhow.
// For example, if the URL is localhost, all `http` requests are valid
// (localhost is a secure context), but we still identify `http` requests
// as an "Allowed" insecure URL.
for (const item of items) {
if (!item.resolution) item.resolution = str_(UIStrings.allowed);
}

let displayValue = '';
if (items.length > 0) {
displayValue = str_(UIStrings.displayValue, {itemCount: items.length});
}

return {
score: Number(items.length === 0),
displayValue,
Expand Down
15 changes: 15 additions & 0 deletions lighthouse-core/lib/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,9 +800,18 @@
"lighthouse-core/audits/installable-manifest.js | title": {
"message": "Web app manifest meets the installability requirements"
},
"lighthouse-core/audits/is-on-https.js | allowed": {
"message": "Allowed"
},
"lighthouse-core/audits/is-on-https.js | blocked": {
"message": "Blocked"
},
"lighthouse-core/audits/is-on-https.js | columnInsecureURL": {
"message": "Insecure URL"
},
"lighthouse-core/audits/is-on-https.js | columnResolution": {
"message": "Request Resolution"
},
"lighthouse-core/audits/is-on-https.js | description": {
"message": "All sites should be protected with HTTPS, even ones that don't handle sensitive data. This includes avoiding [mixed content](https://developers.google.com/web/fundamentals/security/prevent-mixed-content/what-is-mixed-content), where some resources are loaded over HTTP despite the initial request being servedover HTTPS. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs. [Learn more](https://web.dev/is-on-https/)."
},
Expand All @@ -815,6 +824,12 @@
"lighthouse-core/audits/is-on-https.js | title": {
"message": "Uses HTTPS"
},
"lighthouse-core/audits/is-on-https.js | upgraded": {
"message": "Automatically upgraded to HTTPS"
},
"lighthouse-core/audits/is-on-https.js | warning": {
"message": "Allowed with warning"
},
"lighthouse-core/audits/largest-contentful-paint-element.js | description": {
"message": "This is the largest contentful element painted within the viewport. [Learn More](https://web.dev/lighthouse-largest-contentful-paint/)"
},
Expand Down
15 changes: 15 additions & 0 deletions lighthouse-core/lib/i18n/locales/en-XL.json
Original file line number Diff line number Diff line change
Expand Up @@ -800,9 +800,18 @@
"lighthouse-core/audits/installable-manifest.js | title": {
"message": "Ŵéb̂ áp̂ṕ m̂án̂íf̂éŝt́ m̂éêt́ŝ t́ĥé îńŝt́âĺl̂áb̂íl̂ít̂ý r̂éq̂úîŕêḿêńt̂ś"
},
"lighthouse-core/audits/is-on-https.js | allowed": {
"message": "Âĺl̂óŵéd̂"
},
"lighthouse-core/audits/is-on-https.js | blocked": {
"message": "B̂ĺôćk̂éd̂"
},
"lighthouse-core/audits/is-on-https.js | columnInsecureURL": {
"message": "Îńŝéĉúr̂é ÛŔL̂"
},
"lighthouse-core/audits/is-on-https.js | columnResolution": {
"message": "R̂éq̂úêśt̂ Ŕêśôĺût́îón̂"
},
"lighthouse-core/audits/is-on-https.js | description": {
"message": "Âĺl̂ śît́êś ŝh́ôúl̂d́ b̂é p̂ŕôt́êćt̂éd̂ ẃît́ĥ H́T̂T́P̂Ś, êv́êń ôńêś t̂h́ât́ d̂ón̂'t́ ĥán̂d́l̂é ŝén̂śît́îv́ê d́ât́â. T́ĥíŝ ín̂ćl̂úd̂éŝ áv̂óîd́îńĝ [ḿîx́êd́ ĉón̂t́êńt̂](https://developers.google.com/web/fundamentals/security/prevent-mixed-content/what-is-mixed-content), ẃĥér̂é ŝóm̂é r̂éŝóûŕĉéŝ ár̂é l̂óâd́êd́ ôv́êŕ ĤT́T̂Ṕ d̂éŝṕît́ê t́ĥé îńît́îál̂ ŕêq́ûéŝt́ b̂éîńĝ śêŕv̂éd̂óv̂ér̂ H́T̂T́P̂Ś. ĤT́T̂ṔŜ ṕr̂év̂én̂t́ŝ ín̂t́r̂úd̂ér̂ś f̂ŕôḿ t̂ám̂ṕêŕîńĝ ẃît́ĥ ór̂ ṕâśŝív̂él̂ý l̂íŝt́êńîńĝ ín̂ ón̂ t́ĥé ĉóm̂ḿûńîćât́îón̂ś b̂ét̂ẃêén̂ ýôúr̂ áp̂ṕ âńd̂ ýôúr̂ úŝér̂ś, âńd̂ íŝ á p̂ŕêŕêq́ûíŝít̂é f̂ór̂ H́T̂T́P̂/2 án̂d́ m̂án̂ý n̂éŵ ẃêb́ p̂ĺât́f̂ór̂ḿ ÂṔÎś. [L̂éâŕn̂ ḿôŕê](https://web.dev/is-on-https/)."
},
Expand All @@ -815,6 +824,12 @@
"lighthouse-core/audits/is-on-https.js | title": {
"message": "Ûśêś ĤT́T̂ṔŜ"
},
"lighthouse-core/audits/is-on-https.js | upgraded": {
"message": "Âút̂óm̂át̂íĉál̂ĺŷ úp̂ǵr̂ád̂éd̂ t́ô H́T̂T́P̂Ś"
},
"lighthouse-core/audits/is-on-https.js | warning": {
"message": "Âĺl̂óŵéd̂ ẃît́ĥ ẃâŕn̂ín̂ǵ"
},
"lighthouse-core/audits/largest-contentful-paint-element.js | description": {
"message": "T̂h́îś îś t̂h́ê ĺâŕĝéŝt́ ĉón̂t́êńt̂f́ûĺ êĺêḿêńt̂ ṕâín̂t́êd́ ŵít̂h́îń t̂h́ê v́îéŵṕôŕt̂. [Ĺêár̂ń M̂ór̂é](https://web.dev/lighthouse-largest-contentful-paint/)"
},
Expand Down
41 changes: 39 additions & 2 deletions lighthouse-core/test/audits/is-on-https-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ const networkRecordsToDevtoolsLog = require('../network-records-to-devtools-log.
/* eslint-env jest */

describe('Security: HTTPS audit', () => {
function getArtifacts(networkRecords) {
function getArtifacts(networkRecords, mixedContentIssues) {
const devtoolsLog = networkRecordsToDevtoolsLog(networkRecords);
return {
devtoolsLogs: {[Audit.DEFAULT_PASS]: devtoolsLog},
InspectorIssues: {mixedContent: mixedContentIssues || []},
};
}

Expand All @@ -41,7 +42,8 @@ describe('Security: HTTPS audit', () => {
]), {computedCache: new Map()}).then(result => {
assert.strictEqual(result.score, 0);
expect(result.displayValue).toBeDisplayString('1 insecure request found');
assert.deepEqual(result.details.items[0], {url: 'http://insecure.com/image.jpeg'});
expect(result.details.items[0]).toMatchObject({url: 'http://insecure.com/image.jpeg'});
assert.strictEqual(result.details.headings.length, 1);
});
});

Expand All @@ -55,6 +57,41 @@ describe('Security: HTTPS audit', () => {
});
});

it('augmented with mixed-content InspectorIssues', async () => {
const networkRecords = [
{url: 'https://google.com/', parsedURL: {scheme: 'https', host: 'google.com'}},
{url: 'http://localhost/image.jpeg', parsedURL: {scheme: 'http', host: 'localhost'}},
{url: 'http://google.com/', parsedURL: {scheme: 'http', host: 'google.com'}},
];
const mixedContentIssues = [
{insecureURL: 'http://localhost/image.jpeg', resolutionStatus: 'MixedContentBlocked'},
{insecureURL: 'http://localhost/image2.jpeg', resolutionStatus: 'MixedContentBlockedLOL'},
];
const artifacts = getArtifacts(networkRecords, mixedContentIssues);
const result = await Audit.audit(artifacts, {computedCache: new Map()});

expect(result.details.headings).toHaveLength(2);
expect(result.details.items).toHaveLength(3);

expect(result.details.items[0]).toMatchObject({
url: 'http://google.com/',
resolution: expect.toBeDisplayString('Allowed'),
});

expect(result.details.items[1]).toMatchObject({
url: 'http://localhost/image.jpeg',
resolution: expect.toBeDisplayString('Blocked'),
});

// Unknown blocked resolution string is used as fallback.
expect(result.details.items[2]).toMatchObject({
url: 'http://localhost/image2.jpeg',
resolution: expect.toBeDisplayString('MixedContentBlockedLOL'),
});

expect(result.score).toBe(0);
});

describe('#isSecureRecord', () => {
it('correctly identifies insecure records', () => {
assert.strictEqual(Audit.isSecureRecord({parsedURL: {scheme: 'http', host: 'google.com'}}),
Expand Down
3 changes: 2 additions & 1 deletion lighthouse-core/test/config/config-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,14 @@ describe('Config', () => {
gatherers: [
'viewport-dimensions',
'meta-elements',
'inspector-issues',
],
}],
audits: ['is-on-https'],
};

const _ = new Config(configJSON);
assert.equal(configJSON.passes[0].gatherers.length, 2);
assert.equal(configJSON.passes[0].gatherers.length, 3);
});

it('expands audits', () => {
Expand Down
Loading

0 comments on commit 5acd30f

Please sign in to comment.