Skip to content

fix: remove the conditional that prevented some permissions being overridden on windows #529

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

Merged
merged 3 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
104 changes: 104 additions & 0 deletions integration-test/playwright/windows-permissions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { test, expect } from '@playwright/test'
import { readFileSync } from 'fs'
import { mockWindowsMessaging, wrapWindowsScripts } from '@duckduckgo/messaging/lib/test-utils.mjs'

test('Windows Permissions Usage', async ({ page }) => {
const perms = new WindowsPermissionsSpec(page)
await perms.enabled()
const results = await page.evaluate(() => {
// @ts-expect-error - this is added by the test framework
return window.results['Disabled Windows Permissions']
})

for (const result of results) {
expect(result.result).toEqual(result.expected)
}
})

export class WindowsPermissionsSpec {
htmlPage = '/permissions/index.html'
config = './integration-test/test-pages/permissions/config/permissions.json'
build = './build/windows/contentScope.js'
/**
* @param {import("@playwright/test").Page} page
*/
constructor (page) {
this.page = page
}

async enabled () {
await this.installPolyfills()
const config = JSON.parse(readFileSync(this.config, 'utf8'))
await this.setup({ config })
await this.page.goto(this.htmlPage)
}

/**
* In CI, the global objects such as USB might not be installed on the
* version of chromium running there.
*/
async installPolyfills () {
await this.page.addInitScript(() => {
// @ts-expect-error - testing
if (typeof Bluetooth === 'undefined') {
globalThis.Bluetooth = {}
globalThis.Bluetooth.prototype = { requestDevice: async () => { /* noop */ } }
}
// @ts-expect-error - testing
if (typeof USB === 'undefined') {
globalThis.USB = {}
globalThis.USB.prototype = { requestDevice: async () => { /* noop */ } }
}

// @ts-expect-error - testing
if (typeof Serial === 'undefined') {
globalThis.Serial = {}
globalThis.Serial.prototype = { requestPort: async () => { /* noop */ } }
}
// @ts-expect-error - testing
if (typeof HID === 'undefined') {
globalThis.HID = {}
globalThis.HID.prototype = { requestDevice: async () => { /* noop */ } }
}
})
}

/**
* @param {object} params
* @param {Record<string, any>} params.config
* @return {Promise<void>}
*/
async setup (params) {
const { config } = params

// read the built file from disk and do replacements
const injectedJS = wrapWindowsScripts(this.buildArtefact, {
$CONTENT_SCOPE$: config,
$USER_UNPROTECTED_DOMAINS$: [],
$USER_PREFERENCES$: {
platform: { name: 'windows' },
debug: true
}
})

await this.page.addInitScript(mockWindowsMessaging, {
messagingContext: {
env: 'development',
context: 'contentScopeScripts',
featureName: 'n/a'
},
responses: {}
})

// attach the JS
await this.page.addInitScript(injectedJS)
}

/**
* @return {string}
*/
get buildArtefact () {
const buildArtefact = readFileSync(this.build, 'utf8')
return buildArtefact
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"unprotectedTemporary": [],
"features": {
"windowsPermissionUsage": {
"state": "enabled",
"exceptions": []
}
}
}
45 changes: 45 additions & 0 deletions integration-test/test-pages/permissions/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Permissions</title>
<link rel="stylesheet" href="../shared/style.css">
</head>
<body>
<script src="../shared/utils.js"></script>

<p>Windows permission usage</p>

<script>
test('Disabled Windows Permissions', async () => {
const registerProtocolHandler = await captureError(() => Navigator.prototype.registerProtocolHandler())
const requestDevice = await captureError(() => Bluetooth.prototype.requestDevice())
const usbDevice = await captureError(() => USB.prototype.requestDevice())
const serialPort = await captureError(() => Serial.prototype.requestPort())
const hidDevice = await captureError(() => HID.prototype.requestDevice())
const midi = await captureError(() => Navigator.prototype.requestMIDIAccess())

return [
{ name: 'Bluetooth.prototype.requestDevice()', result: requestDevice.toString(), expected: 'Error: Permission denied' },
{ name: 'USB.prototype.requestDevice', result: usbDevice.toString(), expected: 'Error: Permission denied' },
{ name: 'Serial.prototype.requestPort', result: serialPort.toString(), expected: 'Error: Permission denied' },
{ name: 'HID.prototype.requestDevice', result: hidDevice.toString(), expected: 'Error: Permission denied' },
{ name: 'Protocol handler: Navigator.prototype.registerProtocolHandler', result: registerProtocolHandler.toString(), expected: 'Error: Permission denied' },
{ name: 'MIDI: Navigator.prototype.requestMIDIAccess', result: midi.toString(), expected: 'Error: Permission denied' },
];
})

async function captureError(fn) {
try {
// ensure Promise.reject is captured
return fn().catch(e => e)
} catch (e) {
return e
}
}

renderResults();
</script>
</body>
</html>
1 change: 0 additions & 1 deletion src/features/windows-permission-usage.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,6 @@ export default class WindowsPermissionUsage extends ContentFeature {
]
for (const { name, prototype, method, isPromise } of permissionsToDisable) {
try {
if (typeof window[name] === 'undefined') continue
const proxy = new DDGProxy(featureName, prototype(), method, {
apply () {
if (isPromise) {
Expand Down