Skip to content

Commit

Permalink
Malware status support and design updates - macOS (#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgurgel authored Dec 6, 2024
1 parent 7a987ae commit a3dec18
Show file tree
Hide file tree
Showing 33 changed files with 339 additions and 143 deletions.
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")]),

.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 page.getByRole('link', { name: 'Report site as safe' }).click();
}

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
33 changes: 17 additions & 16 deletions integration-tests/Mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,30 +347,31 @@ 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://duckduckgo.com/duckduckgo-help-pages/privacy/phishing-and-malware-protection/');
}

async calledForReportAsSafeLink(urlParam) {
const url = new URL('https://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
50 changes: 40 additions & 10 deletions integration-tests/macos.spec-int.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,46 @@ 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();
await dash.mocks.calledForReportAsSafeLink('https://privacy-test-pages.site/security/badware/malware.html');
});

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://duckduckgo.com/duckduckgo-help-pages/privacy/phishing-and-malware-protection/',
reportSiteAsSafeForm: 'https://duckduckgo.com/malicious-site-protection/report-error',
};
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
9 changes: 6 additions & 3 deletions shared/js/browser/utils/communication-mocks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ export async function mockDataProvider(params) {
}
window.onChangeLocale?.(state.localeSettings);
window.onChangeRequestData(state.url, { requests: state.requests || [] });
window.onChangePhishingStatus?.(state.phishing);

if (platform?.name === 'macos') {
window.onChangeMaliciousSiteStatus?.(state.maliciousSiteStatus);
}
}

export function windowsMockApis() {
Expand Down Expand Up @@ -383,9 +386,9 @@ export function mockBrowserApis(params = { messages: {} }) {
* @return {Promise<void>}
*/
export async function installDebuggerMocks(platform) {
console.log('instaling...');
console.log('installing...');
if (window.__playwright) {
console.log('instaling... NOE');
console.log('installing... NOE');
return console.log('❌ mocked already there');
}
if (platform.name === 'windows') {
Expand Down
Loading

0 comments on commit a3dec18

Please sign in to comment.