From d6628657da65e8400cb8b92a126f07fda72332ec Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 5 Jul 2024 17:15:09 +0200 Subject: [PATCH] throw when proxy is provided --- docs/src/api/params.md | 6 +++- .../src/client/browserContext.ts | 8 +++-- packages/playwright-core/src/client/fetch.ts | 2 +- packages/playwright-core/types/types.d.ts | 24 +++++++++----- packages/playwright/types/test.d.ts | 8 +++-- tests/library/client-certificates.spec.ts | 33 +++++++++++++------ 6 files changed, 54 insertions(+), 27 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index aafabfd377..59dd1a20c3 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -523,7 +523,11 @@ Does not enforce fixed viewport, allows resizing window in the headed mode. - `passphrase` ?<[string]> Passphrase for the private key (PEM or PFX). - `pfx` ?<[string]> PFX or PKCS12 encoded private key and certificate chain. -An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the certificate is issued by a custom certificate authority, the `ca` property should be provided with the path to the file with the certificate authority's certificate. If the certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the URLs that the certificate is valid for. +An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the certificate is issued by a custom certificate authority, the `ignoreHTTPSErrors` needs to be set. If the certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the URLs that the certificate is valid for. + +:::note +Using Client Certificates in combination with Proxy Servers is not supported. +::: ## context-option-useragent - `userAgent` <[string]> diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index a08465c0c0..398f45d156 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -28,7 +28,7 @@ import { Worker } from './worker'; import { Events } from './events'; import { TimeoutSettings } from '../common/timeoutSettings'; import { Waiter } from './waiter'; -import type { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions, StorageState, LaunchOptions, ClientCertificate } from './types'; +import type { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions, StorageState, LaunchOptions } from './types'; import { headersObjectToArray, isRegExp, isString, urlMatchesEqual } from '../utils'; import { mkdirIfNeeded } from '../utils/fileUtils'; import type * as api from '../../types/types'; @@ -529,7 +529,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion, forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors, acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads), - clientCertificates: await toClientCertificatesProtocol(options.clientCertificates), + clientCertificates: await toClientCertificatesProtocol(options.proxy, options.clientCertificates), }; if (!contextParams.recordVideo && options.videosPath) { contextParams.recordVideo = { @@ -550,9 +550,11 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) { return 'deny'; } -export async function toClientCertificatesProtocol(clientCertificates?: ClientCertificate[]): Promise { +export async function toClientCertificatesProtocol(proxy: BrowserContextOptions['proxy'], clientCertificates?: BrowserContextOptions['clientCertificates']): Promise { if (!clientCertificates) return undefined; + if (proxy) + throw new Error('Cannot specify both proxy and clientCertificates'); return await Promise.all(clientCertificates.map(async clientCertificate => { if (clientCertificate.certs.length === 0) throw new Error('No certs specified for url: ' + clientCertificate.url); diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 63f74ef26e..fabf68d6e1 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -76,7 +76,7 @@ export class APIRequest implements api.APIRequest { extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined, storageState, tracesDir, - clientCertificates: await toClientCertificatesProtocol(options.clientCertificates), + clientCertificates: await toClientCertificatesProtocol(options.proxy, options.clientCertificates), })).request); this._contexts.add(context); context._request = this; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index e99f46ae7f..bf2398f79d 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -15593,9 +15593,11 @@ export interface APIRequest { /** * An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the - * certificate is issued by a custom certificate authority, the `ca` property should be provided with the path to the - * file with the certificate authority's certificate. If the certificate is valid only for specific URLs, the `url` - * property should be provided with a glob pattern to match the URLs that the certificate is valid for. + * certificate is issued by a custom certificate authority, the `ignoreHTTPSErrors` needs to be set. If the + * certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the + * URLs that the certificate is valid for. + * + * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. */ clientCertificates?: Array<{ /** @@ -16783,9 +16785,11 @@ export interface Browser extends EventEmitter { /** * An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the - * certificate is issued by a custom certificate authority, the `ca` property should be provided with the path to the - * file with the certificate authority's certificate. If the certificate is valid only for specific URLs, the `url` - * property should be provided with a glob pattern to match the URLs that the certificate is valid for. + * certificate is issued by a custom certificate authority, the `ignoreHTTPSErrors` needs to be set. If the + * certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the + * URLs that the certificate is valid for. + * + * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. */ clientCertificates?: Array<{ /** @@ -20254,9 +20258,11 @@ export interface BrowserContextOptions { /** * An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the - * certificate is issued by a custom certificate authority, the `ca` property should be provided with the path to the - * file with the certificate authority's certificate. If the certificate is valid only for specific URLs, the `url` - * property should be provided with a glob pattern to match the URLs that the certificate is valid for. + * certificate is issued by a custom certificate authority, the `ignoreHTTPSErrors` needs to be set. If the + * certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the + * URLs that the certificate is valid for. + * + * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. */ clientCertificates?: Array<{ /** diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index a230f0d58d..024cd23667 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5204,9 +5204,11 @@ export interface PlaywrightTestOptions { /** * An array of client certificates to be used. Each certificate object must have `cert` and `key` or `pfx` to load the * client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. If the - * certificate is issued by a custom certificate authority, the `ca` property should be provided with the path to the - * file with the certificate authority's certificate. If the certificate is valid only for specific URLs, the `url` - * property should be provided with a glob pattern to match the URLs that the certificate is valid for. + * certificate is issued by a custom certificate authority, the `ignoreHTTPSErrors` needs to be set. If the + * certificate is valid only for specific URLs, the `url` property should be provided with a glob pattern to match the + * URLs that the certificate is valid for. + * + * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * * **Usage** * diff --git a/tests/library/client-certificates.spec.ts b/tests/library/client-certificates.spec.ts index 6d134fa053..ba1b9aa63b 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -19,6 +19,7 @@ import path from 'path'; import { expect, playwrightTest as base } from '../config/browserTest'; import https from 'https'; import type net from 'net'; +import type { BrowserContextOptions } from 'packages/playwright-test'; const test = base.extend<{ serverURL: string, serverURLRewrittenToLocalhost: string }>({ serverURL: async ({ }, use) => { @@ -58,23 +59,35 @@ const test = base.extend<{ serverURL: string, serverURLRewrittenToLocalhost: str test.skip(({ mode }) => mode !== 'default'); const kClientCertificatesDir = path.join(__dirname, '../config/testserver/client-certificates'); -const kValidationSubTests: any[] = [ - [[{ url: 'test', certs: [] }], 'No certs specified for url: test'], - [[{ url: 'test', certs: [{}] }]], 'None of cert, key, passphrase or pfx is specified', - [[{ - url: 'test', certs: [{ +const kValidationSubTests: [BrowserContextOptions, string ][] = [ + [{ clientCertificates: [{ url: 'test', certs: [] }] }, 'No certs specified for url: test'], + [{ clientCertificates: [{ url: 'test', certs: [{}] }] }, 'None of cert, key, passphrase or pfx is specified'], + [{ clientCertificates: [{ + url: 'test', + certs: [{ cert: 'foo', key: 'foo', passphrase: 'foo', pfx: 'foo', }] - }], 'pfx is specified together with cert, key or passphrase'] + }] }, 'pfx is specified together with cert, key or passphrase'], + [{ + proxy: { server: 'http://localhost:8080' }, + clientCertificates: [{ + url: 'test', + certs: [{ + cert: 'foo', + key: 'foo', + passphrase: 'foo', + pfx: 'foo', + }] + }] }, 'Cannot specify both proxy and clientCertificates'], ]; test.describe('fetch', () => { test('validate input', async ({ playwright }) => { - for (const [clientCertificates, expected] of kValidationSubTests) - await expect(playwright.request.newContext({ clientCertificates })).rejects.toThrow(expected); + for (const [contextOptions, expected] of kValidationSubTests) + await expect(playwright.request.newContext(contextOptions)).rejects.toThrow(expected); }); test('should fail with no client certificates provided', async ({ playwright, serverURL }) => { @@ -183,8 +196,8 @@ test.describe('fetch', () => { test.describe('browser', () => { test('validate input', async ({ browser }) => { - for (const [clientCertificates, expected] of kValidationSubTests) - await expect(browser.newContext({ clientCertificates })).rejects.toThrow(expected); + for (const [contextOptions, expected] of kValidationSubTests) + await expect(browser.newContext(contextOptions)).rejects.toThrow(expected); }); test('should keep supporting http', async ({ browser, server }) => {