Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Malware status support and design updates - macOS #250

Merged
merged 8 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,5 @@ let package = Package(
.copy("app/img"),
.copy ("app/public"),
.copy ("app/index.html")]),

shakyShane marked this conversation as resolved.
Show resolved Hide resolved
.testTarget(
name: "PrivacyDashboardTests",
dependencies: ["PrivacyDashboardResources"],
path: "swift-package/Tests"),
]
)
32 changes: 32 additions & 0 deletions integration-tests/DashboardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ export class DashboardPage {
await page.locator('[data-page="connection"]').getByText(text).waitFor();
}

async clickReportAsSafeLink() {
const { page } = this;
await expect(page.getByRole('link', { name: 'Report a site as safe' })).toBeVisible();
}

async clickHelpPageLink() {
const { page } = this;
await page.getByRole('link', { name: 'About our phishing and malware protection' }).click();
}

async hasPhishingIcon() {
const { page } = this;
await expect(page.locator('#key-insight div').nth(1)).toHaveClass(/hero-icon--phishing/);
Expand All @@ -189,6 +199,28 @@ export class DashboardPage {
await expect(page.locator('#main-nav div')).toContainText('Site May Be Deceptive');
}

async hasMalwareIcon() {
const { page } = this;
await expect(page.locator('#key-insight div').nth(1)).toHaveClass(/hero-icon--phishing/);
}

async hasMalwareHeadingText() {
const { page } = this;
await expect(page.getByRole('heading', { name: 'privacy-test-pages.site' })).toBeVisible();
}

async hasMalwareWarningText() {
const { page } = this;
await expect(page.locator('#popup-container')).toContainText(
'This site has been flagged for distributing malware designed to compromise your device or steal your personal information.'
);
}

async hasMalwareStatusText() {
const { page } = this;
await expect(page.locator('#main-nav div')).toContainText('Site May Be Deceptive');
}

async connectionLinkDoesntShow() {
await expect(this.connectInfoLink()).not.toBeVisible();
}
Expand Down
35 changes: 19 additions & 16 deletions integration-tests/Mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,30 +347,33 @@ export class Mocks {
}

async calledForAboutLink() {
return this.calledForOpenURLInNewTab('https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/');
}

async calledForHelpPagesLink() {
return this.calledForOpenURLInNewTab(
'https://dub.duckduckgo.com/pages/duckduckgo/mgurgel-help-pages/privacy/phishing-and-malware-protection/'
);
}

async calledForReportAsSafeLink(urlParam) {
const url = new URL('https://use-devtesting12.duckduckgo.com/malicious-site-protection/report-error');
url.searchParams.set('url', urlParam);

return this.calledForOpenURLInNewTab(url.toString());
}

async calledForOpenURLInNewTab(url) {
if (this.platform.name === 'android') {
const calls = await this.outgoing({ names: ['openInNewTab'] });
expect(calls).toMatchObject([
[
'openInNewTab',
JSON.stringify({
url: 'https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/',
}),
],
]);
expect(calls).toMatchObject([['openInNewTab', JSON.stringify({ url })]]);
return;
}
if (this.platform.name === 'macos' || this.platform.name === 'ios') {
const calls = await this.outgoing({
names: ['privacyDashboardOpenUrlInNewTab'],
});
expect(calls).toMatchObject([
[
'privacyDashboardOpenUrlInNewTab',
{
url: 'https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/',
},
],
]);
expect(calls).toMatchObject([['privacyDashboardOpenUrlInNewTab', { url }]]);
return;
}
throw new Error('unreachable. mockCalledForAboutLink must be handled');
Expand Down
49 changes: 39 additions & 10 deletions integration-tests/macos.spec-int.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,45 @@ test('invalid/missing certificate', { tag: '@screenshots' }, async ({ page }) =>
await dash.showsInvalidCertDetail();
});

test('phishing warning', { tag: '@screenshots' }, async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.webkit(page, { platform: 'macos' });
await dash.addState([testDataStates.phishing]);
await dash.screenshot('phishing-warning.png');
await dash.hasPhishingIcon();
await dash.hasPhishingHeadingText();
await dash.hasPhishingWarningText();
await dash.hasPhishingStatusText();
await dash.connectionLinkDoesntShow();
test.describe('phishing & malware protection', () => {
test('phishing warning', { tag: '@screenshots' }, async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.webkit(page, { platform: 'macos' });
await dash.addState([testDataStates.phishing]);
await dash.screenshot('phishing-warning.png');
await dash.hasPhishingIcon();
await dash.hasPhishingHeadingText();
await dash.hasPhishingWarningText();
await dash.hasPhishingStatusText();
await dash.connectionLinkDoesntShow();
});

test('malware warning', { tag: '@screenshots' }, async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.webkit(page, { platform: 'macos' });
await dash.addState([testDataStates.malware]);
await dash.screenshot('malware-warning.png');
await dash.hasMalwareIcon();
await dash.hasMalwareHeadingText();
await dash.hasMalwareWarningText();
await dash.hasMalwareStatusText();
await dash.connectionLinkDoesntShow();
});

test('shows report as safe link', async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.webkit(page, { platform: 'macos' });
await dash.addState([testDataStates.malware]);
await dash.clickReportAsSafeLink();
});

test('shows help page link', async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.webkit(page, { platform: 'macos' });
await dash.addState([testDataStates.malware]);
await dash.clickHelpPageLink();
await dash.mocks.calledForHelpPagesLink();
});
});

test('insecure certificate', async ({ page }) => {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions schema/__generated__/schema.parsers.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions schema/__generated__/schema.types.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions schema/get-privacy-dashboard-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
"localeSettings": {
"$ref": "./locale.json"
},
"phishingStatus": {
"$ref": "./phishing.json"
"maliciousSiteStatus": {
"$ref": "./malicious-site.json"
},
"parentEntity": { "$ref": "./parent-entity.json" },
"specialDomainName": {
Expand Down
14 changes: 14 additions & 0 deletions schema/malicious-site.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MaliciousSiteStatus",
"type": "object",
"description": "This describes the payload required to set the phishing & malware status",
"additionalProperties": false,
"required": ["kind"],
"properties": {
"kind": {
"description": "Kind of threat detected",
"enum": ["phishing", "malware", null]
}
}
}
14 changes: 0 additions & 14 deletions schema/phishing.json

This file was deleted.

6 changes: 6 additions & 0 deletions shared/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ export const httpsMessages = {
none: 'site:connectionNotSecure.title',
invalid: 'site:connectionNotSecureInvalidCertificate.title',
phishing: 'site:phishingWebsite.title',
malware: 'site:malwareWebsite.title',
};

export const duckDuckGoURLs = {
phishingAndMalwareHelpPage: 'https://dub.duckduckgo.com/pages/duckduckgo/mgurgel-help-pages/privacy/phishing-and-malware-protection/',
reportSiteAsSafeForm: 'https://use-devtesting12.duckduckgo.com/malicious-site-protection/report-error',
};
shakyShane marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 5 additions & 5 deletions shared/js/browser/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,19 @@ export function assert(condition, message = '') {
export function onChangeLocale(payload) {}

/**
* Sets the phishing status for a page. This is a required call.
* Sets the phishing & malware status for a page. This is a required call.
*
* Example Payload: see {@link "Generated Schema Definitions".PhishingStatus}
* Example Payload: see {@link "Generated Schema Definitions".MaliciousSiteStatus}
*
* ```json
* {
* "phishingStatus": true
* "kind": "phishing"
* }
* ```
*
* @param {import('../../../schema/__generated__/schema.types').PhishingStatus} payload
* @param {import('../../../schema/__generated__/schema.types').MaliciousSiteStatus} payload
*/
export function onChangePhishingStatus(payload) {}
export function onChangeMaliciousSiteStatus(payload) {}

/**
* Sets the Feature Settings
Expand Down
28 changes: 14 additions & 14 deletions shared/js/browser/macos-communication.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import invariant from 'tiny-invariant';
import {
cookiePromptManagementStatusSchema,
localeSettingsSchema,
phishingStatusSchema,
maliciousSiteStatusSchema,
protectionsStatusSchema,
requestDataSchema,
toggleReportScreenSchema,
Expand Down Expand Up @@ -61,8 +61,8 @@ let isPendingUpdates;
let parentEntity;
const cookiePromptManagementStatus = {};

/** @type {boolean | undefined} */
let phishingStatus;
/** @type {import('../../../schema/__generated__/schema.types').MaliciousSiteStatus} */
let maliciousSiteStatus;

/** @type {string | undefined} */
let locale;
Expand All @@ -71,7 +71,7 @@ const combineSources = () => ({
tab: Object.assign(
{},
trackerBlockingData || {},
{ phishingStatus: phishingStatus ?? false },
{ maliciousSiteStatus: maliciousSiteStatus ?? false },
{
isPendingUpdates,
parentEntity,
Expand All @@ -89,8 +89,8 @@ const resolveInitialRender = function () {
const isIsProtectedSet = typeof protections !== 'undefined';
const isTrackerBlockingDataSet = typeof trackerBlockingData === 'object';
const isLocaleSet = typeof locale === 'string';
const isPhishingSet = typeof phishingStatus === 'boolean';
if (!isLocaleSet || !isUpgradedHttpsSet || !isIsProtectedSet || !isTrackerBlockingDataSet || !isPhishingSet) {
const isMaliciousSiteSet = isIOS() || (maliciousSiteStatus && maliciousSiteStatus.kind !== undefined);
if (!isLocaleSet || !isUpgradedHttpsSet || !isIsProtectedSet || !isTrackerBlockingDataSet || !isMaliciousSiteSet) {
return;
}
getBackgroundTabDataPromises.forEach((resolve) => resolve(combineSources()));
Expand Down Expand Up @@ -178,24 +178,24 @@ export function onChangeLocale(payload) {
}

/**
* {@inheritDoc common.onChangePhishingStatus}
* @type {import("./common.js").onChangePhishingStatus}
* {@inheritDoc common.onChangeMaliciousSiteStatus}
* @type {import("./common.js").onChangeMaliciousSiteStatus}
* @group macOS -> JavaScript Interface
* @example
*
* ```swift
* // swift
* evaluate(js: "window.onChangePhishingStatus(\(phishingStatusJsonString))", in: webView)
* evaluate(js: "window.onChangeMaliciousSiteStatus(\(maliciousSiteStatusJsonString))", in: webView)
* ```
*/
export function onChangePhishingStatus(payload) {
const parsed = phishingStatusSchema.safeParse(payload);
export function onChangeMaliciousSiteStatus(payload) {
const parsed = maliciousSiteStatusSchema.safeParse(payload);
if (!parsed.success) {
console.error('could not parse incoming data from onChangePhishingStatus');
console.error('could not parse incoming data from onChangeMaliciousSiteStatus');
console.error(parsed.error);
return;
}
phishingStatus = parsed.data.phishingStatus;
maliciousSiteStatus = parsed.data;
resolveInitialRender();
}

Expand Down Expand Up @@ -537,7 +537,7 @@ export function setupShared() {
if (trackerBlockingData) trackerBlockingData.upgradedHttps = upgradedHttps;
resolveInitialRender();
};
window.onChangePhishingStatus = onChangePhishingStatus;
window.onChangeMaliciousSiteStatus = onChangeMaliciousSiteStatus;
window.onChangeProtectionStatus = onChangeProtectionStatus;
window.onChangeLocale = onChangeLocale;
window.onChangeCertificateData = function (data) {
Expand Down
Loading
Loading