mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-06 03:16:17 +03:00
feat: support client certificates (#31529)
Signed-off-by: Max Schmitt <max@schmitt.mx> Co-authored-by: Dmitry Gozman <dgozman@gmail.com>
This commit is contained in:
parent
229000501e
commit
9569cb5c1e
@ -12,6 +12,9 @@ see [APIRequestContext].
|
||||
|
||||
Creates new instances of [APIRequestContext].
|
||||
|
||||
### option: APIRequest.newContext.clientCertificates = %%-context-option-clientCertificates-%%
|
||||
* since: 1.46
|
||||
|
||||
### option: APIRequest.newContext.useragent = %%-context-option-useragent-%%
|
||||
* since: v1.16
|
||||
|
||||
|
@ -256,6 +256,9 @@ await browser.CloseAsync();
|
||||
### option: Browser.newContext.proxy = %%-context-option-proxy-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Browser.newContext.clientCertificates = %%-context-option-clientCertificates-%%
|
||||
* since: 1.46
|
||||
|
||||
### option: Browser.newContext.storageState = %%-js-python-context-option-storage-state-%%
|
||||
* since: v1.8
|
||||
|
||||
@ -281,6 +284,9 @@ testing frameworks should explicitly create [`method: Browser.newContext`] follo
|
||||
### option: Browser.newPage.proxy = %%-context-option-proxy-%%
|
||||
* since: v1.8
|
||||
|
||||
### option: Browser.newPage.clientCertificates = %%-context-option-clientCertificates-%%
|
||||
* since: 1.46
|
||||
|
||||
### option: Browser.newPage.storageState = %%-js-python-context-option-storage-state-%%
|
||||
* since: v1.8
|
||||
|
||||
|
@ -343,6 +343,9 @@ use a temporary directory instead.
|
||||
### option: BrowserType.launchPersistentContext.firefoxUserPrefs2 = %%-csharp-java-browser-option-firefoxuserprefs-%%
|
||||
* since: v1.40
|
||||
|
||||
### option: BrowserType.launchPersistentContext.clientCertificates = %%-context-option-clientCertificates-%%
|
||||
* since: 1.46
|
||||
|
||||
## async method: BrowserType.launchServer
|
||||
* since: v1.8
|
||||
* langs: js
|
||||
|
@ -514,6 +514,25 @@ Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `no_
|
||||
|
||||
Does not enforce fixed viewport, allows resizing window in the headed mode.
|
||||
|
||||
## context-option-clientCertificates
|
||||
- `clientCertificates` <[Array]<[Object]>>
|
||||
- `url` <[string]> Glob pattern to match the URLs that the certificate is valid for.
|
||||
- `certs` <[Array]<[Object]>> List of client certificates to be used.
|
||||
- `certPath` ?<[string]> Path to the file with the certificate in PEM format.
|
||||
- `keyPath` ?<[string]> Path to the file with the private key in PEM format.
|
||||
- `pfxPath` ?<[string]> Path to the PFX or PKCS12 encoded private key and certificate chain.
|
||||
- `passphrase` ?<[string]> Passphrase for the private key (PEM or PFX).
|
||||
|
||||
An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the private key is encrypted. 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.
|
||||
:::
|
||||
|
||||
:::note
|
||||
When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`.
|
||||
:::
|
||||
|
||||
## context-option-useragent
|
||||
- `userAgent` <[string]>
|
||||
|
||||
|
@ -138,6 +138,35 @@ export default defineConfig({
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## property: TestOptions.clientCertificates = %%-context-option-clientCertificates-%%
|
||||
* since: 1.46
|
||||
|
||||
**Usage**
|
||||
|
||||
```js title="playwright.config.ts"
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
projects: [
|
||||
{
|
||||
name: 'Microsoft Edge',
|
||||
use: {
|
||||
...devices['Desktop Edge'],
|
||||
clientCertificates: [{
|
||||
url: 'https://example.com/**',
|
||||
certs: [{
|
||||
certPath: './cert.pem',
|
||||
keyPath: './key.pem',
|
||||
passphase: 'mysecretpassword',
|
||||
}],
|
||||
}],
|
||||
},
|
||||
},
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## property: TestOptions.colorScheme = %%-context-option-colorscheme-%%
|
||||
* since: v1.10
|
||||
|
||||
|
11
packages/playwright-core/bin/socks-certs/README.md
Normal file
11
packages/playwright-core/bin/socks-certs/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Certfificates for Socks Proxy
|
||||
|
||||
These certificates are used when client certificates are used with
|
||||
Playwright. Playwright then creates a Socks proxy, which sits between
|
||||
the browser and the actual target server. The Socks proxy uses this certificiate
|
||||
to talk to the browser and establishes its own secure TLS connection to the server.
|
||||
The certificates are generated via:
|
||||
|
||||
```bash
|
||||
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -keyout key.pem -out cert.pem -subj "/CN=localhost"
|
||||
```
|
19
packages/playwright-core/bin/socks-certs/cert.pem
Normal file
19
packages/playwright-core/bin/socks-certs/cert.pem
Normal file
@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDCTCCAfGgAwIBAgIUTcrzEueVL/OuLHr4LBIPWeS4UL0wDQYJKoZIhvcNAQEL
|
||||
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDcwNDA4NDAzNFoXDTM0MDcw
|
||||
MjA4NDAzNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEApof+SZVN4UGma4xJDVHhMSpmEJoCdMPr+HFadJJK/brF
|
||||
BNOhA1C5wNk8oD/XYo7enAHQH/EsBnq4MMxv79rXTGnIdXMF+43GdMDh5kh81FQy
|
||||
Esw8Vt4eif9eZkjUxI2GHhR2ovJewmQa7E+SeUB2RzJTqz8QPLhd74JFfgaci+S2
|
||||
8L37ScVjcw55T1PcNflzB4vwsQHBT3yND0MLDhm+8MLzmTl4Mw5PgIOaBl5Jh8Tr
|
||||
wQF4eeeB3FPJoMQhTP8aGBjW1mo+NmSSRAPIAZyhmCAnDeC33yRjAaiHjaL5Pr9f
|
||||
wt5zoF5+U1xWhGXWzGOE6p/VTj62F9a2fOXNHclYJQIDAQABo1MwUTAdBgNVHQ4E
|
||||
FgQU9BoVzGtb5x70KqGO/89N1hyqi5kwHwYDVR0jBBgwFoAU9BoVzGtb5x70KqGO
|
||||
/89N1hyqi5kwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYcbI
|
||||
wvcfx2p8z0RNN3EA+epKX1SagZyJX4ORIO8kln1sDU+ceHde3n3xnp1dg6HG2qh1
|
||||
a7CZub/fNUaP9R8+6iiV0wPT7Ybkb2NIJcH1yq+/bfSS5OC5DO0yv9SUADdBoDwa
|
||||
zOuBAqdcYW1BHYcbAzsQnniRcejHu06ioaS6SwwJ8150rQnLT4Lh9LAl40W6v4nZ
|
||||
NdTGQETTrbjcgH1ER4IhWTKtVyPOxGF9A/OOawMEdfS8BhUO7YRS4QNFFaQMrJAb
|
||||
MDhDtjSyDogLr8P43xjjWvQWG9a7zTF0kKEsdJ0cEG5HATpg8bPHmrouxbs2HGeH
|
||||
kJXzMykrsYyXsInN3w==
|
||||
-----END CERTIFICATE-----
|
28
packages/playwright-core/bin/socks-certs/key.pem
Normal file
28
packages/playwright-core/bin/socks-certs/key.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCmh/5JlU3hQaZr
|
||||
jEkNUeExKmYQmgJ0w+v4cVp0kkr9usUE06EDULnA2TygP9dijt6cAdAf8SwGergw
|
||||
zG/v2tdMach1cwX7jcZ0wOHmSHzUVDISzDxW3h6J/15mSNTEjYYeFHai8l7CZBrs
|
||||
T5J5QHZHMlOrPxA8uF3vgkV+BpyL5LbwvftJxWNzDnlPU9w1+XMHi/CxAcFPfI0P
|
||||
QwsOGb7wwvOZOXgzDk+Ag5oGXkmHxOvBAXh554HcU8mgxCFM/xoYGNbWaj42ZJJE
|
||||
A8gBnKGYICcN4LffJGMBqIeNovk+v1/C3nOgXn5TXFaEZdbMY4Tqn9VOPrYX1rZ8
|
||||
5c0dyVglAgMBAAECggEAB6zX4vNPKhUZAvbtvP/rlZUDLDu05kXLX+F1jk7ZxvTv
|
||||
NKg+UQVM8l7wxN/8YM3944nP2lEGuuu4BoO9mvvmlV6Avy0EdxITNflX0AHCQxT4
|
||||
U9Z253gIR0ruQl+T8tUk+8jsqNjr1iC//ukx8oWujdx7b7aR3IKQzcOeyU6rs2TN
|
||||
lyrVVsEaFVi9+wCw0xyiCmPlobrn+egdigw7Zhp2BRinC6W9eMxuPS2hlhQUhBm/
|
||||
eiD96YWp0RAv/L5qO93reoXIAzrrLdcUgPEnnq1zN7y2xihU2+B2sTph1m/A26+J
|
||||
yPcXd7vQrXlRXQU6PaCa+0oJULlpiAzy3HPbnr4BkQKBgQDdmekTX8dQqiEZPX1C
|
||||
017QRFbx0/x/TDFDSeJbDeauMzzCaGqCO2WVmYmTvFtby2G4/6BYowVtJVHm4uJl
|
||||
XsYk8dWIQGLPIj1Cw7ZieJvb2EVRxgnY2oMaOTOazHzPHFzZV718zwEeZrryT82J
|
||||
881E8wgM8V3DjkS4ye3TbwvimQKBgQDAYa/IdnpAg5z1TREi9Tt8fnoGpmSscAak
|
||||
USgeXVsvoNzXXkE94MiiCOOrX1r68TWYDAzq6MKGDewkWOfLwXWR6D5C2LyE1q9P
|
||||
1pxstgs/nC3ZUTz0yEH47ahSmhywhGlvXXOQEXUSLiVTOdeMCubMqwQW80F1868n
|
||||
aBHcj5/lbQKBgQDIojjsWaNT3TTqbUmj30vQtI8jlBLgDlPr4FEYr5VT0wAH5BHK
|
||||
p4xpzgFJyRfOHG312TuMBM087LUinfjsXsp3WJ1EJ0dO0mk0sY3HyfsTKNRaHTt9
|
||||
Ixnf/DpExS+bNMq73Tyqa6FPrSNFkAtAA4SuEHwRe9aw33ZI+EpjS/8uwQKBgQCi
|
||||
9NwqSLlLVnColEw0uVdXH+cLJPzX19i4bQo3lkp8MJ2ATJWk7XflUPRQoGf3ckQ8
|
||||
c9CpVtoXJUnmi+xkeo21Nu0uQFqHhzZewWIk75rdmdR4ZUjl649+ZQkUVviASNjq
|
||||
fVU7Lp5k9POm6LL9K+rOaPoA2rKTUAQItC2VD4+YjQKBgB6kgvgN6Mz/u0RE3kkV
|
||||
2GOoP5sso71Hxwh7o6JEzUMhR+e/T/LLcBwEjLYcf1FYRySHsXLn2Ar/Uw1J7pAZ
|
||||
ud54/at+7mTDliaT8Ar7S9vcso7ZfmuDX9qB9+c77idPskVBPo2tjJbwvFcB6sww
|
||||
5Elcfmj6tEP4YLJ6Kv3qTPhT
|
||||
-----END PRIVATE KEY-----
|
@ -529,6 +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),
|
||||
};
|
||||
if (!contextParams.recordVideo && options.videosPath) {
|
||||
contextParams.recordVideo = {
|
||||
@ -548,3 +549,21 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
|
||||
return 'accept';
|
||||
return 'deny';
|
||||
}
|
||||
|
||||
export async function toClientCertificatesProtocol(clientCertificates?: BrowserContextOptions['clientCertificates']): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
|
||||
if (!clientCertificates)
|
||||
return undefined;
|
||||
return await Promise.all(clientCertificates.map(async clientCertificate => {
|
||||
return {
|
||||
url: clientCertificate.url,
|
||||
certs: await Promise.all(clientCertificate.certs.map(async cert => {
|
||||
return {
|
||||
cert: cert.certPath ? await fs.promises.readFile(cert.certPath) : undefined,
|
||||
key: cert.keyPath ? await fs.promises.readFile(cert.keyPath) : undefined,
|
||||
pfx: cert.pfxPath ? await fs.promises.readFile(cert.pfxPath) : undefined,
|
||||
passphrase: cert.passphrase,
|
||||
};
|
||||
}))
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
@ -25,10 +25,11 @@ import { assert, headersObjectToArray, isString } from '../utils';
|
||||
import { mkdirIfNeeded } from '../utils/fileUtils';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { RawHeaders } from './network';
|
||||
import type { FilePayload, Headers, StorageState } from './types';
|
||||
import type { ClientCertificate, FilePayload, Headers, StorageState } from './types';
|
||||
import type { Playwright } from './playwright';
|
||||
import { Tracing } from './tracing';
|
||||
import { TargetClosedError, isTargetClosedError } from './errors';
|
||||
import { toClientCertificatesProtocol } from './browserContext';
|
||||
|
||||
export type FetchOptions = {
|
||||
params?: { [key: string]: string; },
|
||||
@ -44,9 +45,10 @@ export type FetchOptions = {
|
||||
maxRetries?: number,
|
||||
};
|
||||
|
||||
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'storageState' | 'tracesDir'> & {
|
||||
type NewContextOptions = Omit<channels.PlaywrightNewRequestOptions, 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'tracesDir'> & {
|
||||
extraHTTPHeaders?: Headers,
|
||||
storageState?: string | StorageState,
|
||||
clientCertificates?: ClientCertificate[];
|
||||
};
|
||||
|
||||
type RequestWithBodyOptions = Omit<FetchOptions, 'method'>;
|
||||
@ -74,6 +76,7 @@ export class APIRequest implements api.APIRequest {
|
||||
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
|
||||
storageState,
|
||||
tracesDir,
|
||||
clientCertificates: await toClientCertificatesProtocol(options.clientCertificates),
|
||||
})).request);
|
||||
this._contexts.add(context);
|
||||
context._request = this;
|
||||
@ -175,7 +178,7 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
|
||||
const params = objectToArray(options.params);
|
||||
const method = options.method || options.request?.method();
|
||||
// Cannot call allHeaders() here as the request may be paused inside route handler.
|
||||
const headersObj = options.headers || options.request?.headers() ;
|
||||
const headersObj = options.headers || options.request?.headers();
|
||||
const headers = headersObj ? headersObjectToArray(headersObj) : undefined;
|
||||
let jsonData: any;
|
||||
let formData: channels.NameValue[] | undefined;
|
||||
@ -395,7 +398,7 @@ function isJsonContentType(headers?: HeadersArray): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function objectToArray(map?: { [key: string]: any }): NameValue[] | undefined {
|
||||
function objectToArray(map?: { [key: string]: any }): NameValue[] | undefined {
|
||||
if (!map)
|
||||
return undefined;
|
||||
const result = [];
|
||||
|
@ -47,7 +47,17 @@ export type SetStorageState = {
|
||||
export type LifecycleEvent = channels.LifecycleEvent;
|
||||
export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domcontentloaded', 'networkidle', 'commit']);
|
||||
|
||||
export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'viewport' | 'noDefaultViewport' | 'extraHTTPHeaders' | 'storageState' | 'recordHar' | 'colorScheme' | 'reducedMotion' | 'forcedColors' | 'acceptDownloads'> & {
|
||||
export type ClientCertificate = {
|
||||
url: string;
|
||||
certs: {
|
||||
certPath?: string;
|
||||
keyPath?: string;
|
||||
pfxPath?: string;
|
||||
passphrase?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'viewport' | 'noDefaultViewport' | 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'recordHar' | 'colorScheme' | 'reducedMotion' | 'forcedColors' | 'acceptDownloads'> & {
|
||||
viewport?: Size | null;
|
||||
extraHTTPHeaders?: Headers;
|
||||
logger?: Logger;
|
||||
@ -70,6 +80,7 @@ export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'vie
|
||||
reducedMotion?: 'reduce' | 'no-preference' | null;
|
||||
forcedColors?: 'active' | 'none' | null;
|
||||
acceptDownloads?: boolean;
|
||||
clientCertificates?: ClientCertificate[];
|
||||
};
|
||||
|
||||
type LaunchOverrides = {
|
||||
|
@ -331,6 +331,15 @@ scheme.PlaywrightNewRequestParams = tObject({
|
||||
userAgent: tOptional(tString),
|
||||
ignoreHTTPSErrors: tOptional(tBoolean),
|
||||
extraHTTPHeaders: tOptional(tArray(tType('NameValue'))),
|
||||
clientCertificates: tOptional(tArray(tObject({
|
||||
url: tString,
|
||||
certs: tArray(tObject({
|
||||
cert: tOptional(tBinary),
|
||||
key: tOptional(tBinary),
|
||||
passphrase: tOptional(tString),
|
||||
pfx: tOptional(tBinary),
|
||||
})),
|
||||
}))),
|
||||
httpCredentials: tOptional(tObject({
|
||||
username: tString,
|
||||
password: tString,
|
||||
@ -532,6 +541,15 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
|
||||
height: tNumber,
|
||||
})),
|
||||
ignoreHTTPSErrors: tOptional(tBoolean),
|
||||
clientCertificates: tOptional(tArray(tObject({
|
||||
url: tString,
|
||||
certs: tArray(tObject({
|
||||
cert: tOptional(tBinary),
|
||||
key: tOptional(tBinary),
|
||||
passphrase: tOptional(tString),
|
||||
pfx: tOptional(tBinary),
|
||||
})),
|
||||
}))),
|
||||
javaScriptEnabled: tOptional(tBoolean),
|
||||
bypassCSP: tOptional(tBoolean),
|
||||
userAgent: tOptional(tString),
|
||||
@ -614,6 +632,15 @@ scheme.BrowserNewContextParams = tObject({
|
||||
height: tNumber,
|
||||
})),
|
||||
ignoreHTTPSErrors: tOptional(tBoolean),
|
||||
clientCertificates: tOptional(tArray(tObject({
|
||||
url: tString,
|
||||
certs: tArray(tObject({
|
||||
cert: tOptional(tBinary),
|
||||
key: tOptional(tBinary),
|
||||
passphrase: tOptional(tString),
|
||||
pfx: tOptional(tBinary),
|
||||
})),
|
||||
}))),
|
||||
javaScriptEnabled: tOptional(tBoolean),
|
||||
bypassCSP: tOptional(tBoolean),
|
||||
userAgent: tOptional(tString),
|
||||
@ -676,6 +703,15 @@ scheme.BrowserNewContextForReuseParams = tObject({
|
||||
height: tNumber,
|
||||
})),
|
||||
ignoreHTTPSErrors: tOptional(tBoolean),
|
||||
clientCertificates: tOptional(tArray(tObject({
|
||||
url: tString,
|
||||
certs: tArray(tObject({
|
||||
cert: tOptional(tBinary),
|
||||
key: tOptional(tBinary),
|
||||
passphrase: tOptional(tString),
|
||||
pfx: tOptional(tBinary),
|
||||
})),
|
||||
}))),
|
||||
javaScriptEnabled: tOptional(tBoolean),
|
||||
bypassCSP: tOptional(tBoolean),
|
||||
userAgent: tOptional(tString),
|
||||
@ -2513,6 +2549,15 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({
|
||||
height: tNumber,
|
||||
})),
|
||||
ignoreHTTPSErrors: tOptional(tBoolean),
|
||||
clientCertificates: tOptional(tArray(tObject({
|
||||
url: tString,
|
||||
certs: tArray(tObject({
|
||||
cert: tOptional(tBinary),
|
||||
key: tOptional(tBinary),
|
||||
passphrase: tOptional(tString),
|
||||
pfx: tOptional(tBinary),
|
||||
})),
|
||||
}))),
|
||||
javaScriptEnabled: tOptional(tBoolean),
|
||||
bypassCSP: tOptional(tBoolean),
|
||||
userAgent: tOptional(tString),
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import type * as types from './types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import { BrowserContext, validateBrowserContextOptions } from './browserContext';
|
||||
import { BrowserContext, createClientCertificatesProxyIfNeeded, validateBrowserContextOptions } from './browserContext';
|
||||
import { Page } from './page';
|
||||
import { Download } from './download';
|
||||
import type { ProxySettings } from './types';
|
||||
@ -84,7 +84,15 @@ export abstract class Browser extends SdkObject {
|
||||
|
||||
async newContext(metadata: CallMetadata, options: channels.BrowserNewContextParams): Promise<BrowserContext> {
|
||||
validateBrowserContextOptions(options, this.options);
|
||||
const context = await this.doCreateNewContext(options);
|
||||
const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(options, this.options);
|
||||
let context;
|
||||
try {
|
||||
context = await this.doCreateNewContext(options);
|
||||
} catch (error) {
|
||||
await clientCertificatesProxy?.close();
|
||||
throw error;
|
||||
}
|
||||
context._clientCertificatesProxy = clientCertificatesProxy;
|
||||
if (options.storageState)
|
||||
await context.setStorageState(metadata, options.storageState);
|
||||
return context;
|
||||
|
@ -43,6 +43,7 @@ import * as consoleApiSource from '../generated/consoleApiSource';
|
||||
import { BrowserContextAPIRequestContext } from './fetch';
|
||||
import type { Artifact } from './artifact';
|
||||
import { Clock } from './clock';
|
||||
import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
|
||||
|
||||
export abstract class BrowserContext extends SdkObject {
|
||||
static Events = {
|
||||
@ -90,6 +91,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
private _debugger!: Debugger;
|
||||
_closeReason: string | undefined;
|
||||
readonly clock: Clock;
|
||||
_clientCertificatesProxy: ClientCertificatesProxy | undefined;
|
||||
|
||||
constructor(browser: Browser, options: channels.BrowserNewContextParams, browserContextId: string | undefined) {
|
||||
super(browser, 'browser-context');
|
||||
@ -245,6 +247,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
// at the same time.
|
||||
return;
|
||||
}
|
||||
this._clientCertificatesProxy?.close().catch(() => {});
|
||||
this.tracing.abort();
|
||||
if (this._isPersistentContext)
|
||||
this.onClosePersistent();
|
||||
@ -655,6 +658,18 @@ export function assertBrowserContextIsNotOwned(context: BrowserContext) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createClientCertificatesProxyIfNeeded(options: channels.BrowserNewContextOptions, browserOptions?: BrowserOptions) {
|
||||
if (!options.clientCertificates?.length)
|
||||
return;
|
||||
if (options.proxy?.server || browserOptions?.proxy?.server)
|
||||
throw new Error('Cannot specify both proxy and clientCertificates');
|
||||
verifyClientCertificates(options.clientCertificates);
|
||||
const clientCertificatesProxy = new ClientCertificatesProxy(options);
|
||||
options.proxy = { server: await clientCertificatesProxy.listen() };
|
||||
options.ignoreHTTPSErrors = true;
|
||||
return clientCertificatesProxy;
|
||||
}
|
||||
|
||||
export function validateBrowserContextOptions(options: channels.BrowserNewContextParams, browserOptions: BrowserOptions) {
|
||||
if (options.noDefaultViewport && options.deviceScaleFactor !== undefined)
|
||||
throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
|
||||
@ -707,6 +722,27 @@ export function verifyGeolocation(geolocation?: types.Geolocation) {
|
||||
throw new Error(`geolocation.accuracy: precondition 0 <= ACCURACY failed.`);
|
||||
}
|
||||
|
||||
export function verifyClientCertificates(clientCertificates?: channels.BrowserNewContextParams['clientCertificates']) {
|
||||
if (!clientCertificates)
|
||||
return;
|
||||
for (const { url, certs } of clientCertificates) {
|
||||
if (!url)
|
||||
throw new Error(`clientCertificates.url is required`);
|
||||
if (!certs.length)
|
||||
throw new Error('No certs specified for url: ' + url);
|
||||
for (const cert of certs) {
|
||||
if (!cert.cert && !cert.key && !cert.passphrase && !cert.pfx)
|
||||
throw new Error('None of cert, key, passphrase or pfx is specified');
|
||||
if (cert.cert && !cert.key)
|
||||
throw new Error('cert is specified without key');
|
||||
if (!cert.cert && cert.key)
|
||||
throw new Error('key is specified without cert');
|
||||
if (cert.pfx && (cert.cert || cert.key))
|
||||
throw new Error('pfx is specified together with cert, key or passphrase');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeProxySettings(proxy: types.ProxySettings): types.ProxySettings {
|
||||
let { server, bypass } = proxy;
|
||||
let url;
|
||||
|
@ -18,7 +18,7 @@ import fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import path from 'path';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
||||
import { createClientCertificatesProxyIfNeeded, normalizeProxySettings, validateBrowserContextOptions } from './browserContext';
|
||||
import type { BrowserName } from './registry';
|
||||
import { registry } from './registry';
|
||||
import type { ConnectionTransport } from './transport';
|
||||
@ -77,10 +77,17 @@ export abstract class BrowserType extends SdkObject {
|
||||
async launchPersistentContext(metadata: CallMetadata, userDataDir: string, options: channels.BrowserTypeLaunchPersistentContextOptions & { useWebSocket?: boolean }): Promise<BrowserContext> {
|
||||
options = this._validateLaunchOptions(options);
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const persistent: channels.BrowserNewContextParams = options;
|
||||
const persistent: channels.BrowserNewContextParams = { ...options };
|
||||
controller.setLogName('browser');
|
||||
const browser = await controller.run(progress => {
|
||||
return this._innerLaunchWithRetries(progress, options, persistent, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); });
|
||||
const browser = await controller.run(async progress => {
|
||||
// Note: Any initial TLS requests will fail since we rely on the Page/Frames initialize which sets ignoreHTTPSErrors.
|
||||
const clientCertificatesProxy = await createClientCertificatesProxyIfNeeded(persistent);
|
||||
if (clientCertificatesProxy)
|
||||
options.proxy = persistent.proxy;
|
||||
progress.cleanupWhenAborted(() => clientCertificatesProxy?.close());
|
||||
const browser = await this._innerLaunchWithRetries(progress, options, persistent, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); });
|
||||
browser._defaultContext!._clientCertificatesProxy = clientCertificatesProxy;
|
||||
return browser;
|
||||
}, TimeoutSettings.launchTimeout(options));
|
||||
return browser._defaultContext!;
|
||||
}
|
||||
|
@ -16,8 +16,9 @@
|
||||
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type { LookupAddress } from 'dns';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import http from 'http';
|
||||
import fs from 'fs';
|
||||
import https from 'https';
|
||||
import type { Readable, TransformCallback } from 'stream';
|
||||
import { pipeline, Transform } from 'stream';
|
||||
import url from 'url';
|
||||
@ -27,7 +28,7 @@ import { TimeoutSettings } from '../common/timeoutSettings';
|
||||
import { getUserAgent } from '../utils/userAgent';
|
||||
import { assert, createGuid, monotonicTime } from '../utils';
|
||||
import { HttpsProxyAgent, SocksProxyAgent } from '../utilsBundle';
|
||||
import { BrowserContext } from './browserContext';
|
||||
import { BrowserContext, verifyClientCertificates } from './browserContext';
|
||||
import { CookieStore, domainMatches } from './cookieStore';
|
||||
import { MultipartFormData } from './formData';
|
||||
import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent } from '../utils/happy-eyeballs';
|
||||
@ -40,6 +41,7 @@ import { Tracing } from './trace/recorder/tracing';
|
||||
import type * as types from './types';
|
||||
import type { HeadersArray, ProxySettings } from './types';
|
||||
import { kMaxCookieExpiresDateInSeconds } from './network';
|
||||
import { clientCertificatesToTLSOptions } from './socksClientCertificatesInterceptor';
|
||||
|
||||
type FetchRequestOptions = {
|
||||
userAgent: string;
|
||||
@ -49,6 +51,7 @@ type FetchRequestOptions = {
|
||||
timeoutSettings: TimeoutSettings;
|
||||
ignoreHTTPSErrors?: boolean;
|
||||
baseURL?: string;
|
||||
clientCertificates?: channels.BrowserNewContextOptions['clientCertificates'];
|
||||
};
|
||||
|
||||
type HeadersObject = Readonly<{ [name: string]: string }>;
|
||||
@ -190,9 +193,12 @@ export abstract class APIRequestContext extends SdkObject {
|
||||
maxRedirects: params.maxRedirects === 0 ? -1 : params.maxRedirects === undefined ? 20 : params.maxRedirects,
|
||||
timeout,
|
||||
deadline,
|
||||
...clientCertificatesToTLSOptions(this._defaultOptions().clientCertificates, requestUrl.toString()),
|
||||
__testHookLookup: (params as any).__testHookLookup,
|
||||
};
|
||||
// rejectUnauthorized = undefined is treated as true in node 12.
|
||||
if (process.env.PWTEST_UNSUPPORTED_CUSTOM_CA)
|
||||
options.ca = [fs.readFileSync(process.env.PWTEST_UNSUPPORTED_CUSTOM_CA)];
|
||||
// rejectUnauthorized = undefined is treated as true in Node.js 12.
|
||||
if (params.ignoreHTTPSErrors || defaults.ignoreHTTPSErrors)
|
||||
options.rejectUnauthorized = false;
|
||||
|
||||
@ -351,6 +357,7 @@ export abstract class APIRequestContext extends SdkObject {
|
||||
maxRedirects: options.maxRedirects - 1,
|
||||
timeout: options.timeout,
|
||||
deadline: options.deadline,
|
||||
...clientCertificatesToTLSOptions(this._defaultOptions().clientCertificates, url.toString()),
|
||||
__testHookLookup: options.__testHookLookup,
|
||||
};
|
||||
// rejectUnauthorized = undefined is treated as true in node 12.
|
||||
@ -522,6 +529,7 @@ export class BrowserContextAPIRequestContext extends APIRequestContext {
|
||||
timeoutSettings: this._context._timeoutSettings,
|
||||
ignoreHTTPSErrors: this._context._options.ignoreHTTPSErrors,
|
||||
baseURL: this._context._options.baseURL,
|
||||
clientCertificates: this._context._options.clientCertificates,
|
||||
};
|
||||
}
|
||||
|
||||
@ -557,17 +565,21 @@ export class GlobalAPIRequestContext extends APIRequestContext {
|
||||
if (!/^\w+:\/\//.test(url))
|
||||
url = 'http://' + url;
|
||||
proxy.server = url;
|
||||
if (options.clientCertificates)
|
||||
throw new Error('Cannot specify both proxy and clientCertificates');
|
||||
}
|
||||
if (options.storageState) {
|
||||
this._origins = options.storageState.origins;
|
||||
this._cookieStore.addCookies(options.storageState.cookies || []);
|
||||
}
|
||||
verifyClientCertificates(options.clientCertificates);
|
||||
this._options = {
|
||||
baseURL: options.baseURL,
|
||||
userAgent: options.userAgent || getUserAgent(),
|
||||
extraHTTPHeaders: options.extraHTTPHeaders,
|
||||
ignoreHTTPSErrors: !!options.ignoreHTTPSErrors,
|
||||
httpCredentials: options.httpCredentials,
|
||||
clientCertificates: options.clientCertificates,
|
||||
proxy,
|
||||
timeoutSettings,
|
||||
};
|
||||
|
@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the 'License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type net from 'net';
|
||||
import path from 'path';
|
||||
import type https from 'https';
|
||||
import fs from 'fs';
|
||||
import tls from 'tls';
|
||||
import stream from 'stream';
|
||||
import { createSocket } from '../utils/happy-eyeballs';
|
||||
import { globToRegex } from '../utils';
|
||||
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy';
|
||||
import { SocksProxy } from '../common/socksProxy';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
||||
class SocksConnectionDuplex extends stream.Duplex {
|
||||
constructor(private readonly writeCallback: (data: Buffer) => void) {
|
||||
super();
|
||||
}
|
||||
override _read(): void { }
|
||||
override _write(chunk: Buffer, encoding: BufferEncoding, callback: (error?: Error | null | undefined) => void): void {
|
||||
this.writeCallback(chunk);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
class SocksProxyConnection {
|
||||
private readonly socksProxy: ClientCertificatesProxy;
|
||||
private readonly uid: string;
|
||||
private readonly host: string;
|
||||
private readonly port: number;
|
||||
firstPackageReceived: boolean = false;
|
||||
target!: net.Socket;
|
||||
// In case of http, we just pipe data to the target socket and they are |undefined|.
|
||||
internal: stream.Duplex | undefined;
|
||||
internalTLS: tls.TLSSocket | undefined;
|
||||
|
||||
constructor(socksProxy: ClientCertificatesProxy, uid: string, host: string, port: number) {
|
||||
this.socksProxy = socksProxy;
|
||||
this.uid = uid;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this.target = await createSocket(this.host === 'local.playwright' ? 'localhost' : this.host, this.port);
|
||||
this.target.on('close', () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid }));
|
||||
this.target.on('error', error => this.socksProxy._socksProxy.sendSocketError({ uid: this.uid, error: error.message }));
|
||||
this.socksProxy._socksProxy.socketConnected({
|
||||
uid: this.uid,
|
||||
host: this.target.localAddress!,
|
||||
port: this.target.localPort!,
|
||||
});
|
||||
}
|
||||
|
||||
public onClose() {
|
||||
this.internal?.destroy();
|
||||
this.target.destroy();
|
||||
}
|
||||
|
||||
public onData(data: Buffer) {
|
||||
// HTTP / TLS are client-hello based protocols. This allows us to detect
|
||||
// the protocol on the first package and attach appropriate listeners.
|
||||
if (!this.firstPackageReceived) {
|
||||
this.firstPackageReceived = true;
|
||||
// 0x16 is SSLv3/TLS "handshake" content type: https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_record
|
||||
if (data[0] === 0x16)
|
||||
this._attachTLSListeners();
|
||||
else
|
||||
this.target.on('data', data => this.socksProxy._socksProxy.sendSocketData({ uid: this.uid, data }));
|
||||
}
|
||||
if (this.internal)
|
||||
this.internal.push(data);
|
||||
else
|
||||
this.target.write(data);
|
||||
}
|
||||
|
||||
private _attachTLSListeners() {
|
||||
this.internal = new SocksConnectionDuplex(data => this.socksProxy._socksProxy.sendSocketData({ uid: this.uid, data }));
|
||||
const internalTLS = new tls.TLSSocket(this.internal, {
|
||||
isServer: true,
|
||||
key: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/key.pem')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/cert.pem')),
|
||||
});
|
||||
this.internalTLS = internalTLS;
|
||||
internalTLS.on('close', () => this.socksProxy._socksProxy.sendSocketEnd({ uid: this.uid }));
|
||||
|
||||
const targetTLS = tls.connect({
|
||||
socket: this.target,
|
||||
rejectUnauthorized: this.socksProxy.contextOptions.ignoreHTTPSErrors === true ? false : true,
|
||||
...clientCertificatesToTLSOptions(this.socksProxy.contextOptions.clientCertificates, `https://${this.host}:${this.port}/`),
|
||||
});
|
||||
|
||||
internalTLS.pipe(targetTLS);
|
||||
targetTLS.pipe(internalTLS);
|
||||
|
||||
// Handle close and errors
|
||||
const closeBothSockets = () => {
|
||||
internalTLS.end();
|
||||
targetTLS.end();
|
||||
};
|
||||
|
||||
internalTLS.on('end', () => closeBothSockets());
|
||||
targetTLS.on('end', () => closeBothSockets());
|
||||
|
||||
internalTLS.on('error', () => closeBothSockets());
|
||||
targetTLS.on('error', error => {
|
||||
internalTLS.write('HTTP/1.1 503 Internal Server Error\r\n');
|
||||
internalTLS.write('Content-Type: text/html; charset=utf-8\r\n');
|
||||
const responseBody = 'Playwright client-certificate error: ' + error.message;
|
||||
internalTLS.write('Content-Length: ' + Buffer.byteLength(responseBody) + '\r\n');
|
||||
internalTLS.write('\r\n');
|
||||
internalTLS.write(responseBody);
|
||||
internalTLS.end();
|
||||
closeBothSockets();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientCertificatesProxy {
|
||||
_socksProxy: SocksProxy;
|
||||
private _connections: Map<string, SocksProxyConnection> = new Map();
|
||||
|
||||
constructor(
|
||||
public readonly contextOptions: Pick<channels.BrowserNewContextOptions, 'clientCertificates' | 'ignoreHTTPSErrors'>
|
||||
) {
|
||||
this._socksProxy = new SocksProxy();
|
||||
this._socksProxy.setPattern('*');
|
||||
this._socksProxy.addListener(SocksProxy.Events.SocksRequested, async (payload: SocksSocketRequestedPayload) => {
|
||||
try {
|
||||
const connection = new SocksProxyConnection(this, payload.uid, payload.host, payload.port);
|
||||
await connection.connect();
|
||||
this._connections.set(payload.uid, connection);
|
||||
} catch (error) {
|
||||
this._socksProxy.socketFailed({ uid: payload.uid, errorCode: error.code });
|
||||
}
|
||||
});
|
||||
this._socksProxy.addListener(SocksProxy.Events.SocksData, async (payload: SocksSocketDataPayload) => {
|
||||
this._connections.get(payload.uid)?.onData(payload.data);
|
||||
});
|
||||
this._socksProxy.addListener(SocksProxy.Events.SocksClosed, (payload: SocksSocketClosedPayload) => {
|
||||
this._connections.get(payload.uid)?.onClose();
|
||||
this._connections.delete(payload.uid);
|
||||
});
|
||||
}
|
||||
|
||||
public async listen(): Promise<string> {
|
||||
const port = await this._socksProxy.listen(0, '127.0.0.1');
|
||||
return `socks5://127.0.0.1:${port}`;
|
||||
}
|
||||
|
||||
public async close() {
|
||||
await this._socksProxy.close();
|
||||
}
|
||||
}
|
||||
|
||||
const kClientCertificatesGlobRegex = Symbol('kClientCertificatesGlobRegex');
|
||||
|
||||
export function clientCertificatesToTLSOptions(
|
||||
clientCertificates: channels.BrowserNewContextOptions['clientCertificates'],
|
||||
requestURL: string
|
||||
): Pick<https.RequestOptions, 'pfx' | 'key' | 'cert'> | undefined {
|
||||
const matchingCerts = clientCertificates?.filter(c => {
|
||||
let regex: RegExp | undefined = (c as any)[kClientCertificatesGlobRegex];
|
||||
if (!regex) {
|
||||
regex = globToRegex(c.url);
|
||||
(c as any)[kClientCertificatesGlobRegex] = regex;
|
||||
}
|
||||
regex.lastIndex = 0;
|
||||
return regex.test(requestURL);
|
||||
});
|
||||
if (!matchingCerts || !matchingCerts.length)
|
||||
return;
|
||||
const tlsOptions = {
|
||||
pfx: [] as { buf: Buffer, passphrase?: string }[],
|
||||
key: [] as { pem: Buffer, passphrase?: string }[],
|
||||
cert: [] as Buffer[],
|
||||
};
|
||||
for (const { certs } of matchingCerts) {
|
||||
for (const cert of certs) {
|
||||
if (cert.cert)
|
||||
tlsOptions.cert.push(cert.cert);
|
||||
if (cert.key)
|
||||
tlsOptions.key.push({ pem: cert.key, passphrase: cert.passphrase });
|
||||
if (cert.pfx)
|
||||
tlsOptions.pfx.push({ buf: cert.pfx, passphrase: cert.passphrase });
|
||||
}
|
||||
}
|
||||
return tlsOptions;
|
||||
}
|
172
packages/playwright-core/types/types.d.ts
vendored
172
packages/playwright-core/types/types.d.ts
vendored
@ -13221,6 +13221,49 @@ export interface BrowserType<Unused = {}> {
|
||||
*/
|
||||
chromiumSandbox?: boolean;
|
||||
|
||||
/**
|
||||
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
|
||||
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
|
||||
* private key is encrypted. 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.
|
||||
*
|
||||
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
* work by replacing `localhost` with `local.playwright`.
|
||||
*/
|
||||
clientCertificates?: Array<{
|
||||
/**
|
||||
* Glob pattern to match the URLs that the certificate is valid for.
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* List of client certificates to be used.
|
||||
*/
|
||||
certs: Array<{
|
||||
/**
|
||||
* Path to the file with the certificate in PEM format.
|
||||
*/
|
||||
certPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the file with the private key in PEM format.
|
||||
*/
|
||||
keyPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the PFX or PKCS12 encoded private key and certificate chain.
|
||||
*/
|
||||
pfxPath?: string;
|
||||
|
||||
/**
|
||||
* Passphrase for the private key (PEM or PFX).
|
||||
*/
|
||||
passphrase?: string;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See
|
||||
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details.
|
||||
@ -15590,6 +15633,49 @@ export interface APIRequest {
|
||||
*/
|
||||
baseURL?: string;
|
||||
|
||||
/**
|
||||
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
|
||||
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
|
||||
* private key is encrypted. 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.
|
||||
*
|
||||
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
* work by replacing `localhost` with `local.playwright`.
|
||||
*/
|
||||
clientCertificates?: Array<{
|
||||
/**
|
||||
* Glob pattern to match the URLs that the certificate is valid for.
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* List of client certificates to be used.
|
||||
*/
|
||||
certs: Array<{
|
||||
/**
|
||||
* Path to the file with the certificate in PEM format.
|
||||
*/
|
||||
certPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the file with the private key in PEM format.
|
||||
*/
|
||||
keyPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the PFX or PKCS12 encoded private key and certificate chain.
|
||||
*/
|
||||
pfxPath?: string;
|
||||
|
||||
/**
|
||||
* Passphrase for the private key (PEM or PFX).
|
||||
*/
|
||||
passphrase?: string;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
|
||||
*/
|
||||
@ -16741,6 +16827,49 @@ export interface Browser extends EventEmitter {
|
||||
*/
|
||||
bypassCSP?: boolean;
|
||||
|
||||
/**
|
||||
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
|
||||
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
|
||||
* private key is encrypted. 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.
|
||||
*
|
||||
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
* work by replacing `localhost` with `local.playwright`.
|
||||
*/
|
||||
clientCertificates?: Array<{
|
||||
/**
|
||||
* Glob pattern to match the URLs that the certificate is valid for.
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* List of client certificates to be used.
|
||||
*/
|
||||
certs: Array<{
|
||||
/**
|
||||
* Path to the file with the certificate in PEM format.
|
||||
*/
|
||||
certPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the file with the private key in PEM format.
|
||||
*/
|
||||
keyPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the PFX or PKCS12 encoded private key and certificate chain.
|
||||
*/
|
||||
pfxPath?: string;
|
||||
|
||||
/**
|
||||
* Passphrase for the private key (PEM or PFX).
|
||||
*/
|
||||
passphrase?: string;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See
|
||||
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details.
|
||||
@ -20173,6 +20302,49 @@ export interface BrowserContextOptions {
|
||||
*/
|
||||
bypassCSP?: boolean;
|
||||
|
||||
/**
|
||||
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
|
||||
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
|
||||
* private key is encrypted. 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.
|
||||
*
|
||||
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
* work by replacing `localhost` with `local.playwright`.
|
||||
*/
|
||||
clientCertificates?: Array<{
|
||||
/**
|
||||
* Glob pattern to match the URLs that the certificate is valid for.
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* List of client certificates to be used.
|
||||
*/
|
||||
certs: Array<{
|
||||
/**
|
||||
* Path to the file with the certificate in PEM format.
|
||||
*/
|
||||
certPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the file with the private key in PEM format.
|
||||
*/
|
||||
keyPath?: string;
|
||||
|
||||
/**
|
||||
* Path to the PFX or PKCS12 encoded private key and certificate chain.
|
||||
*/
|
||||
pfxPath?: string;
|
||||
|
||||
/**
|
||||
* Passphrase for the private key (PEM or PFX).
|
||||
*/
|
||||
passphrase?: string;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See
|
||||
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details.
|
||||
|
@ -140,6 +140,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }],
|
||||
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }],
|
||||
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }],
|
||||
clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true }],
|
||||
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }],
|
||||
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true }],
|
||||
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === undefined ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }],
|
||||
@ -155,6 +156,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
_combinedContextOptions: [async ({
|
||||
acceptDownloads,
|
||||
bypassCSP,
|
||||
clientCertificates,
|
||||
colorScheme,
|
||||
deviceScaleFactor,
|
||||
extraHTTPHeaders,
|
||||
@ -209,6 +211,8 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||
options.proxy = proxy;
|
||||
if (storageState !== undefined)
|
||||
options.storageState = storageState;
|
||||
if (clientCertificates?.length)
|
||||
options.clientCertificates = resolveClientCerticates(clientCertificates);
|
||||
if (timezoneId !== undefined)
|
||||
options.timezoneId = timezoneId;
|
||||
if (userAgent !== undefined)
|
||||
@ -416,6 +420,28 @@ function attachConnectedHeaderIfNeeded(testInfo: TestInfo, browser: Browser | nu
|
||||
}
|
||||
}
|
||||
|
||||
function resolveFileToConfig(file: string | undefined) {
|
||||
const config = test.info().config.configFile;
|
||||
if (!config || !file)
|
||||
return file;
|
||||
if (path.isAbsolute(file))
|
||||
return file;
|
||||
return path.resolve(path.dirname(config), file);
|
||||
}
|
||||
|
||||
type ClientCertificates = NonNullable<PlaywrightTestOptions['clientCertificates']>;
|
||||
|
||||
function resolveClientCerticates(clientCertificates: ClientCertificates): ClientCertificates {
|
||||
for (const { certs } of clientCertificates) {
|
||||
for (const cert of certs) {
|
||||
cert.certPath = resolveFileToConfig(cert.certPath);
|
||||
cert.keyPath = resolveFileToConfig(cert.keyPath);
|
||||
cert.pfxPath = resolveFileToConfig(cert.pfxPath);
|
||||
}
|
||||
}
|
||||
return clientCertificates;
|
||||
}
|
||||
|
||||
const kTracingStarted = Symbol('kTracingStarted');
|
||||
const kIsReusedContext = Symbol('kReusedContext');
|
||||
|
||||
|
40
packages/playwright/types/test.d.ts
vendored
40
packages/playwright/types/test.d.ts
vendored
@ -4823,6 +4823,7 @@ export type Fixtures<T extends KeyValue = {}, W extends KeyValue = {}, PT extend
|
||||
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||
type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>;
|
||||
type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
|
||||
type ClientCertificate = Exclude<BrowserContextOptions['clientCertificates'], undefined>[0];
|
||||
type ExtraHTTPHeaders = Exclude<BrowserContextOptions['extraHTTPHeaders'], undefined>;
|
||||
type Proxy = Exclude<BrowserContextOptions['proxy'], undefined>;
|
||||
type StorageState = Exclude<BrowserContextOptions['storageState'], undefined>;
|
||||
@ -5200,6 +5201,45 @@ export interface PlaywrightTestOptions {
|
||||
*
|
||||
*/
|
||||
colorScheme: ColorScheme;
|
||||
/**
|
||||
* An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a
|
||||
* single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the
|
||||
* private key is encrypted. 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.
|
||||
*
|
||||
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
|
||||
* work by replacing `localhost` with `local.playwright`.
|
||||
*
|
||||
* **Usage**
|
||||
*
|
||||
* ```js
|
||||
* // playwright.config.ts
|
||||
* import { defineConfig } from '@playwright/test';
|
||||
*
|
||||
* export default defineConfig({
|
||||
* projects: [
|
||||
* {
|
||||
* name: 'Microsoft Edge',
|
||||
* use: {
|
||||
* ...devices['Desktop Edge'],
|
||||
* clientCertificates: [{
|
||||
* url: 'https://example.com/**',
|
||||
* certs: [{
|
||||
* certPath: './cert.pem',
|
||||
* keyPath: './key.pem',
|
||||
* passphase: 'mysecretpassword',
|
||||
* }],
|
||||
* }],
|
||||
* },
|
||||
* },
|
||||
* ]
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
clientCertificates: ClientCertificate[] | undefined;
|
||||
/**
|
||||
* Specify device scale factor (can be thought of as dpr). Defaults to `1`. Learn more about
|
||||
* [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices).
|
||||
|
@ -576,6 +576,15 @@ export type PlaywrightNewRequestParams = {
|
||||
userAgent?: string,
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
extraHTTPHeaders?: NameValue[],
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
httpCredentials?: {
|
||||
username: string,
|
||||
password: string,
|
||||
@ -600,6 +609,15 @@ export type PlaywrightNewRequestOptions = {
|
||||
userAgent?: string,
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
extraHTTPHeaders?: NameValue[],
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
httpCredentials?: {
|
||||
username: string,
|
||||
password: string,
|
||||
@ -944,6 +962,15 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
||||
height: number,
|
||||
},
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
javaScriptEnabled?: boolean,
|
||||
bypassCSP?: boolean,
|
||||
userAgent?: string,
|
||||
@ -1017,6 +1044,15 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
||||
height: number,
|
||||
},
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
javaScriptEnabled?: boolean,
|
||||
bypassCSP?: boolean,
|
||||
userAgent?: string,
|
||||
@ -1131,6 +1167,15 @@ export type BrowserNewContextParams = {
|
||||
height: number,
|
||||
},
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
javaScriptEnabled?: boolean,
|
||||
bypassCSP?: boolean,
|
||||
userAgent?: string,
|
||||
@ -1190,6 +1235,15 @@ export type BrowserNewContextOptions = {
|
||||
height: number,
|
||||
},
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
javaScriptEnabled?: boolean,
|
||||
bypassCSP?: boolean,
|
||||
userAgent?: string,
|
||||
@ -1252,6 +1306,15 @@ export type BrowserNewContextForReuseParams = {
|
||||
height: number,
|
||||
},
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
javaScriptEnabled?: boolean,
|
||||
bypassCSP?: boolean,
|
||||
userAgent?: string,
|
||||
@ -1311,6 +1374,15 @@ export type BrowserNewContextForReuseOptions = {
|
||||
height: number,
|
||||
},
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
javaScriptEnabled?: boolean,
|
||||
bypassCSP?: boolean,
|
||||
userAgent?: string,
|
||||
@ -4558,6 +4630,15 @@ export type AndroidDeviceLaunchBrowserParams = {
|
||||
height: number,
|
||||
},
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
javaScriptEnabled?: boolean,
|
||||
bypassCSP?: boolean,
|
||||
userAgent?: string,
|
||||
@ -4615,6 +4696,15 @@ export type AndroidDeviceLaunchBrowserOptions = {
|
||||
height: number,
|
||||
},
|
||||
ignoreHTTPSErrors?: boolean,
|
||||
clientCertificates?: {
|
||||
url: string,
|
||||
certs: {
|
||||
cert?: Binary,
|
||||
key?: Binary,
|
||||
passphrase?: string,
|
||||
pfx?: Binary,
|
||||
}[],
|
||||
}[],
|
||||
javaScriptEnabled?: boolean,
|
||||
bypassCSP?: boolean,
|
||||
userAgent?: string,
|
||||
|
@ -433,6 +433,21 @@ ContextOptions:
|
||||
width: number
|
||||
height: number
|
||||
ignoreHTTPSErrors: boolean?
|
||||
clientCertificates:
|
||||
type: array?
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
url: string
|
||||
certs:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
cert: binary?
|
||||
key: binary?
|
||||
passphrase: string?
|
||||
pfx: binary?
|
||||
javaScriptEnabled: boolean?
|
||||
bypassCSP: boolean?
|
||||
userAgent: string?
|
||||
@ -673,6 +688,21 @@ Playwright:
|
||||
extraHTTPHeaders:
|
||||
type: array?
|
||||
items: NameValue
|
||||
clientCertificates:
|
||||
type: array?
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
url: string
|
||||
certs:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
cert: binary?
|
||||
key: binary?
|
||||
passphrase: string?
|
||||
pfx: binary?
|
||||
httpCredentials:
|
||||
type: object?
|
||||
properties:
|
||||
|
59
tests/assets/client-certificates/README.md
Normal file
59
tests/assets/client-certificates/README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Client Certificate test-certificates
|
||||
|
||||
## Server
|
||||
|
||||
```bash
|
||||
openssl req \
|
||||
-x509 \
|
||||
-newkey rsa:4096 \
|
||||
-keyout server/server_key.pem \
|
||||
-out server/server_cert.pem \
|
||||
-nodes \
|
||||
-days 365 \
|
||||
-subj "/CN=localhost/O=Client\ Certificate\ Demo"
|
||||
```
|
||||
|
||||
## Trusted client-certificate (server signed/valid)
|
||||
|
||||
```
|
||||
mkdir -p client/trusted
|
||||
# generate server-signed (valid) certifcate
|
||||
openssl req \
|
||||
-newkey rsa:4096 \
|
||||
-keyout client/trusted/key.pem \
|
||||
-out client/trusted/csr.pem \
|
||||
-nodes \
|
||||
-days 365 \
|
||||
-subj "/CN=Alice"
|
||||
|
||||
# sign with server_cert.pem
|
||||
openssl x509 \
|
||||
-req \
|
||||
-in client/trusted/csr.pem \
|
||||
-CA server/server_cert.pem \
|
||||
-CAkey server/server_key.pem \
|
||||
-out client/trusted/cert.pem \
|
||||
-set_serial 01 \
|
||||
-days 365
|
||||
```
|
||||
|
||||
## Self-signed certificate (invalid)
|
||||
|
||||
```
|
||||
mkdir -p client/self-signed
|
||||
openssl req \
|
||||
-newkey rsa:4096 \
|
||||
-keyout client/self-signed/key.pem \
|
||||
-out client/self-signed/csr.pem \
|
||||
-nodes \
|
||||
-days 365 \
|
||||
-subj "/CN=Bob"
|
||||
|
||||
# sign with self-signed/key.pem
|
||||
openssl x509 \
|
||||
-req \
|
||||
-in client/self-signed/csr.pem \
|
||||
-signkey client/self-signed/key.pem \
|
||||
-out client/self-signed/cert.pem \
|
||||
-days 365
|
||||
```
|
28
tests/assets/client-certificates/client/self-signed/cert.pem
Normal file
28
tests/assets/client-certificates/client/self-signed/cert.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEyzCCArOgAwIBAgIUBO3H8U57HcsnWmwP2Xm7wDgHnmkwDQYJKoZIhvcNAQEL
|
||||
BQAwDjEMMAoGA1UEAwwDQm9iMB4XDTI0MDYyNDEyMzEyOVoXDTI1MDYyNDEyMzEy
|
||||
OVowDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
|
||||
AgEAugNWP3UMpf33fOykU7vrIMv1LS32KZjloQnW6o6t5kjt/Yr3JDKnEuMMshpC
|
||||
4YGTEBJ0y5errBbCP3bQY62SnARAgXAaatRqGF+rqiKEnTDCXQo6ex27nna2LgjU
|
||||
JjpU7uqW3fIbECUDIG2zXpE+9VJ1CWViGrRFN7M57zoJ4SEutyMSu9Z8qeZ+/sHm
|
||||
UjqLgimjxx5KSLe6wx9BI7266cgVLbGLMEzZYvDz3+TznzcfTiUs0h74LBDlQYjh
|
||||
76f2td3gZZ7jii2toJgV+E72tjIOANkLVtIVgx0le/4dgwgoIEkjTm5bvbTijIuh
|
||||
O01TYjoV+r+aeTnl+uGGLGTG9KtMj+HOpDxAapFxLS8sM2kn9bBu+6XdP2sa89PX
|
||||
7AmZ1VjsMFILM20kvbOmfnKrVKTszo68LBeMIcYfR60KaPc/ZglvHFE7lxB4rOBz
|
||||
4QNCNMDciEh6npkntjGnO6q01DhYdBpccW65mAqX0+LrkjpUcBneYoTyd0Izd5M1
|
||||
QNm6WaL2Uye+wmNBIptq8I0A6PiWdVbef3xcF70I9JTJHFODHjs1PDov/llZpUyh
|
||||
cNx8WC+y/nPdt+XS0BR/ap0QWJ66CNa5tsLs6txdx6Aoa373bq6HlY/RRlvkhPxr
|
||||
OOKfon7qKQCQGPTSuSvw8qssehcko79chpaYTPJKaJZRK0sCAwEAAaMhMB8wHQYD
|
||||
VR0OBBYEFBcJPRVNgMzwnvzLnNpsVlQySkWpMA0GCSqGSIb3DQEBCwUAA4ICAQAu
|
||||
KjNiFaTPS4vRwva/kNY5MUBcTH0U3BGPOlWyvrudc3FM8+X7OhGpBLLAGmk/H+KT
|
||||
Kzd2B3btr1AAxprLMehxXVlSF0+5A2DLyNDq2wwoI+V2APpeeGA4cCRsL91ZqCH8
|
||||
57T/XpRWKKorlY17yfxO9GUFJGdl1Oki3wvOOaXWcuxUb7nHOjI7oguUcR4jfdIR
|
||||
WfeUe3P+Y8TLVe2WRdJYEdRfpKN2T+8dGNdVHJ3GokgoLQsj8wpKHM0cd81f+mMf
|
||||
jKfP++mR9w+UCRKgWxbCTFMhZvz4BKwpmLI0mLphumiWkfFJPrxTUmx/0JFfOLdi
|
||||
pDHqd5JfSeiBm+hKTWlY/kc7rPSe+VYiXM+Zs+4EIqjowjiRixW+lRU4lf+7ZDlm
|
||||
v+mi4C4JLHW6I7H085GlM+A6BmEPPBRNx2OOPEIqqLCRkJMi3RmS6X6jkAcCdhmn
|
||||
MsxEKjgG8dJn+kfDGxD1Vfz5PjqzFhHPyahdkXo2br8P3RH7jPY9lb6nvAKupHlV
|
||||
GgKJodeibtbZl1eeCrwdHjrawmQ7VhyQ91Dk7PP+z9h6fQqFxxULYP6WC0P7KVfD
|
||||
EBauab6AJdpwKPqsRL9w87yjl2481aeMdtezYjE1HuWTlObq9YcArbpE8Rm5gLb1
|
||||
9bZ5aZKz62m+O3LZMbsMDusgTeyZFMlB0yA32q+SGg==
|
||||
-----END CERTIFICATE-----
|
26
tests/assets/client-certificates/client/self-signed/csr.pem
Normal file
26
tests/assets/client-certificates/client/self-signed/csr.pem
Normal file
@ -0,0 +1,26 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIEUzCCAjsCAQAwDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOC
|
||||
Ag8AMIICCgKCAgEAugNWP3UMpf33fOykU7vrIMv1LS32KZjloQnW6o6t5kjt/Yr3
|
||||
JDKnEuMMshpC4YGTEBJ0y5errBbCP3bQY62SnARAgXAaatRqGF+rqiKEnTDCXQo6
|
||||
ex27nna2LgjUJjpU7uqW3fIbECUDIG2zXpE+9VJ1CWViGrRFN7M57zoJ4SEutyMS
|
||||
u9Z8qeZ+/sHmUjqLgimjxx5KSLe6wx9BI7266cgVLbGLMEzZYvDz3+TznzcfTiUs
|
||||
0h74LBDlQYjh76f2td3gZZ7jii2toJgV+E72tjIOANkLVtIVgx0le/4dgwgoIEkj
|
||||
Tm5bvbTijIuhO01TYjoV+r+aeTnl+uGGLGTG9KtMj+HOpDxAapFxLS8sM2kn9bBu
|
||||
+6XdP2sa89PX7AmZ1VjsMFILM20kvbOmfnKrVKTszo68LBeMIcYfR60KaPc/Zglv
|
||||
HFE7lxB4rOBz4QNCNMDciEh6npkntjGnO6q01DhYdBpccW65mAqX0+LrkjpUcBne
|
||||
YoTyd0Izd5M1QNm6WaL2Uye+wmNBIptq8I0A6PiWdVbef3xcF70I9JTJHFODHjs1
|
||||
PDov/llZpUyhcNx8WC+y/nPdt+XS0BR/ap0QWJ66CNa5tsLs6txdx6Aoa373bq6H
|
||||
lY/RRlvkhPxrOOKfon7qKQCQGPTSuSvw8qssehcko79chpaYTPJKaJZRK0sCAwEA
|
||||
AaAAMA0GCSqGSIb3DQEBCwUAA4ICAQAFGvffXDPy6TKwrdQShBTCd2sH6lTW7K3D
|
||||
2AzL8ZWLC20QmQtz3YbyDODnauo/8EKGvCpvTHZZoPQbmKgFro9vbLMp++2npvY8
|
||||
9VD7FENkfnlyYspiaLicmmV+wN8MwDgKhnZYM41GnkxUrDCj8iOmFK3bmvvhOD1H
|
||||
1SCWhnWG6VdaWhIbE0faXzK7+0WhHILaWxZTgVAHKavQ3APYEh2+s1UwseugNBsL
|
||||
1p+ldROFK/SIisyzi009a24/Ccan9peJbmmWKKUF7oGqoIYfoeDMPpCG+rWxzpaG
|
||||
1S4DNAVgupLpX/oapzBZs3+mXCfh9NSkjKW0Z+M2yO0qOrhdnO2tdpQ2693JoxsG
|
||||
mFhNtfno+22pBHUY6w9jIFIxNbNMWlS54fZoCg7pgJ0YmME7zuoeu2IUeDb6MSoE
|
||||
fd4S+iGdIhyb83yafe9ws+6u7Es7/ivrU2E5E9dtae11liuGQvYxqIpR5ArGndwr
|
||||
Kv3czIKmkR6R8a7UPRXRona5do8L4uhBYz8SnEaH3ClW80NttDO5F3geHM+znTCS
|
||||
uc3ruF3fl4h6rh7khDBY7c62jA1F8f+plAgjrCAI6ZSyNUdAMpIzwayGE+i/7M92
|
||||
ivWpiPd4neB3ZSP3T6fNlz2SjnmTMwvGHcqvnYAohdqMqrn9MzA4lLX+/Qg+CeRS
|
||||
m3dDmOG0Kw==
|
||||
-----END CERTIFICATE REQUEST-----
|
52
tests/assets/client-certificates/client/self-signed/key.pem
Normal file
52
tests/assets/client-certificates/client/self-signed/key.pem
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
n8nUUVeSi2z5bdctVUaQjQqc6zKI
|
||||
-----END PRIVATE KEY-----
|
29
tests/assets/client-certificates/client/trusted/cert.pem
Normal file
29
tests/assets/client-certificates/client/trusted/cert.pem
Normal file
@ -0,0 +1,29 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFAzCCAuugAwIBAgIBATANBgkqhkiG9w0BAQsFADA2MRIwEAYDVQQDDAlsb2Nh
|
||||
bGhvc3QxIDAeBgNVBAoMF0NsaWVudCBDZXJ0aWZpY2F0ZSBEZW1vMB4XDTI0MDYy
|
||||
NDEyMzEyM1oXDTI1MDYyNDEyMzEyM1owEDEOMAwGA1UEAwwFQWxpY2UwggIiMA0G
|
||||
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC4FGkFO0MTc2xgk8N9lrE8JW5MJQyx
|
||||
me+h4HUqc+FwOddY3EH4akCKuvgmHP8bmB76yg7hsWiMHzNW4dqb+ZpxGaA1FInY
|
||||
2gS/GHPaKmVH+lqtNeIAczu3gNo5yBEVp3GQmv0GGxwp5/ugOu+INfPtPqHEKdRm
|
||||
+Ti7uXvMAeohh4raAv3hx3N2x+AnXpEa4ocz8fXYh4y3puk86KCA9Zq5O7IwB6QC
|
||||
qBxSHdtamniqIzuevouXGd//ZCqjK8P57HqVZiBep/wByQlAN6WTRtjaPwPRT97A
|
||||
YM0ayH91aaQ8BtqSWu2LoXkILV+SRSqLRqbyB4TUaKg0RaG4Fk04vg4xjOnjUNcZ
|
||||
TEAKmAozQXTtazYqXGAgd5VENKk93wK1/B3zGnQBBmyyFsGbkCRZLPkxhXMIhoDc
|
||||
ZcS4qUqVfA+gtv6+KlgiPBD4D5lGEKFPVV03T0v8+elq61+RsWHVa8VO3afFqFvT
|
||||
i8yYwDS8udqGaE5EN7lyl1cYJwafH2mdiNdnQc4/Wp/zqgyPALjCbatyu3PAbTfK
|
||||
DleGBRBfxiLKUB39Jo2bafClqRYQnCdQA1vQX0OF2Q9ZTrsLhskVsVQZN5C/Yopc
|
||||
RsMpDWq7HPAP3WndVeawCBg6QFwnR0z6nmfi+UDz1hiSyl6bf3JJs3Nigll1W7UU
|
||||
J56zv89nS8M4xwIDAQABo0IwQDAdBgNVHQ4EFgQUo4T4xMvLao//h+6AcFhXMe5l
|
||||
iGkwHwYDVR0jBBgwFoAUCtaPQfMWTFHrjCUREWMGqIWYC/MwDQYJKoZIhvcNAQEL
|
||||
BQADggIBABoH28SHV5P1xsrsBtEx8ab0BhSPHuzEI8ytA0KgwojrXRKX5MsLbLyY
|
||||
9lFGH7fu+8830oDnfIe7989JMDGtccLO6DXWoWZjBVTkHPNjPSjF6TrhfqjeqNJH
|
||||
NsPW9XUn3UB2hEYJ1nLLAzYizXYVSfL5HExF6Ph7fAzP6AvSZPS6AsVHXh2/JPvz
|
||||
E/JzbeEewDsv3mB+DqUDne7Ukj/IIegGVx190KXwyLmgXlFd69R9GEVEO4FcXqpA
|
||||
NCCcCmnTzBtzrgYVyWB7vDigMoD2bKWlQXwCgthR6DKywtKpSm9UWilghbgkuzam
|
||||
toAOzIyZFGBt+44MnV/486XhbbIXzlixhSZPgSeOKM1tI69OOZ7ilvR6VxvPOrws
|
||||
ZsUJDWFYYXq++8uzWRe+nHglXEQc+4CWbqqhjnncqh94/jLpHNS+BcrgDK3BuVrq
|
||||
Skj8bNq8xnXgUPr+i9Rzmd4lRefKyjVehbRNXj4gp1Ap/TDQbl/A5Vdb31pTLYvO
|
||||
SvDQXImm5HqV5KyddvKnWCIYVD7ATzcvjLqJl52ykDz4eRorzWQ41K5uwd0SxEyB
|
||||
JZ0MesoWyEaWRlpjzqCNxCwO7Pp6RLMxDvC4/lkRt+7HxLLDWaw7bHplFm24GAYm
|
||||
ebC5La1SdbYKC8AahYwT5Ppn6ThiSqV1m3GsuzgBLjfkYMFGjRQQ
|
||||
-----END CERTIFICATE-----
|
26
tests/assets/client-certificates/client/trusted/csr.pem
Normal file
26
tests/assets/client-certificates/client/trusted/csr.pem
Normal file
@ -0,0 +1,26 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIEVTCCAj0CAQAwEDEOMAwGA1UEAwwFQWxpY2UwggIiMA0GCSqGSIb3DQEBAQUA
|
||||
A4ICDwAwggIKAoICAQC4FGkFO0MTc2xgk8N9lrE8JW5MJQyxme+h4HUqc+FwOddY
|
||||
3EH4akCKuvgmHP8bmB76yg7hsWiMHzNW4dqb+ZpxGaA1FInY2gS/GHPaKmVH+lqt
|
||||
NeIAczu3gNo5yBEVp3GQmv0GGxwp5/ugOu+INfPtPqHEKdRm+Ti7uXvMAeohh4ra
|
||||
Av3hx3N2x+AnXpEa4ocz8fXYh4y3puk86KCA9Zq5O7IwB6QCqBxSHdtamniqIzue
|
||||
vouXGd//ZCqjK8P57HqVZiBep/wByQlAN6WTRtjaPwPRT97AYM0ayH91aaQ8BtqS
|
||||
Wu2LoXkILV+SRSqLRqbyB4TUaKg0RaG4Fk04vg4xjOnjUNcZTEAKmAozQXTtazYq
|
||||
XGAgd5VENKk93wK1/B3zGnQBBmyyFsGbkCRZLPkxhXMIhoDcZcS4qUqVfA+gtv6+
|
||||
KlgiPBD4D5lGEKFPVV03T0v8+elq61+RsWHVa8VO3afFqFvTi8yYwDS8udqGaE5E
|
||||
N7lyl1cYJwafH2mdiNdnQc4/Wp/zqgyPALjCbatyu3PAbTfKDleGBRBfxiLKUB39
|
||||
Jo2bafClqRYQnCdQA1vQX0OF2Q9ZTrsLhskVsVQZN5C/YopcRsMpDWq7HPAP3Wnd
|
||||
VeawCBg6QFwnR0z6nmfi+UDz1hiSyl6bf3JJs3Nigll1W7UUJ56zv89nS8M4xwID
|
||||
AQABoAAwDQYJKoZIhvcNAQELBQADggIBAG7XNJcmByEF0rvSV6bY28PrfirPt09K
|
||||
dTsKNlv9a798k9eq/vqpnVrNslEFj/SVTBPl5r5FIYnueNiO54VlA6nJ+1yRSlvI
|
||||
2SGvgCRoD4xNcMyJgzMwmxovhNRHdheRP+A82EUfgoT8/HCm0UasTw1PcZKokprb
|
||||
T93pie4iV3CWBtVHd9/hZXsMnumT/LIbnUdCOkAsy7cIVsxOHNLZociJeV3LxIgL
|
||||
B5HQdDHPd9i4C6zFgZmm7imrzvFrFk+ksUx1jUeMTXCCvwVyj5cPRbxKduFLCOIA
|
||||
tP61RVeXt6ru5IZl+2n8GApKT9zkPhiGNmCdG54Z7Yc62fvijneeOlKPAwWRbxEq
|
||||
smdW0fOaf73n0eguZ8ujk2ZLVDb2knqk5MoPTuEaSea/haVg/vgP8O5sFwjOcJ6P
|
||||
D1flxGJafOGF0B0hnZXqFjUV2Ty+R5iSk7vaLiwc9asMybqyuEVv5q2/6gmoawt1
|
||||
6srZq/Z3jyaseOzYgeOugZcKZyZNpDgS5ZIq+iKu5fP5gcnmvhZca4fFqd2jYthO
|
||||
v6Xrd5tMcgpcZSRWAezh4p0AICnmdTlpQNcw6rdfr7RAT2OwxQmqW/SbniJCr/Nn
|
||||
W4v6KhZ7mYBrYt7ywVXuhZKmubwShZTprSEsQko5Tujk41/kLJl0C3kIdkMvMWzF
|
||||
gKrkAP6tuQQ7
|
||||
-----END CERTIFICATE REQUEST-----
|
52
tests/assets/client-certificates/client/trusted/key.pem
Normal file
52
tests/assets/client-certificates/client/trusted/key.pem
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
XJVwXoZ9PUMxrfgEI9YmZdHOSJdU9Qc=
|
||||
-----END PRIVATE KEY-----
|
31
tests/assets/client-certificates/server/server_cert.pem
Normal file
31
tests/assets/client-certificates/server/server_cert.pem
Normal file
@ -0,0 +1,31 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFTTCCAzWgAwIBAgIUWdMb2scNR1R1TxDuI4RrFt7mGOwwDQYJKoZIhvcNAQEL
|
||||
BQAwNjESMBAGA1UEAwwJbG9jYWxob3N0MSAwHgYDVQQKDBdDbGllbnQgQ2VydGlm
|
||||
aWNhdGUgRGVtbzAeFw0yNDA2MjQxMjMxMTNaFw0yNTA2MjQxMjMxMTNaMDYxEjAQ
|
||||
BgNVBAMMCWxvY2FsaG9zdDEgMB4GA1UECgwXQ2xpZW50IENlcnRpZmljYXRlIERl
|
||||
bW8wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9VMWaGggaYorXO5IF
|
||||
qj3s+W4nvrzzi3LHYjnugFnUAruOYdXbBboieSR2wnJeITyVp0MbVEsrcfwsCmlB
|
||||
RP3ehheJqW2GtDZ8eFTzZ/r2Npl/TQgGn5/R+HYe9NkXfWAT+VanURcNrP98eSRI
|
||||
zDS/Rx5ZgG9/iYRbB/Lcd+Stkp2UTLDlMTvAv8CFwFLE1bwHmcAqYBMMqEpKh3Z5
|
||||
oshmOSQz28A5o3UK2XCJptywV0lbugs0bBSQiayBs2BzMLbUFiWyh45LEvuWLYpv
|
||||
3FKQwMJi+ZBZ0lOXjFOfW0TXlZRLMSrSfnOP0dq8x2QAFEIIKGwgbZXyCLP+BVLD
|
||||
IJ39+AadSjXaJ+12FkOE8ETe6EEIKoydwzFnxtndMvflKYFILRF8vJp44MTJ/cZ2
|
||||
9ZSY7QPVoSEK9KjtedlmYSvyWYYxtoWN5zR/K7Oyb/lFy45oKU75LuWt5qx9X2eA
|
||||
uScy8SWgo17SN1IF1OdOYjExRXmjnHcQJIAPLfX6hkheee9S8uiWy8a42iBJW4ZV
|
||||
QSsGtbMb3BojjykZYkhTdjmjvjy16IqlJ4JEcRGKxuuvmlCXOs33D1ot7xb1GHwX
|
||||
oLvXo2IOSZAjDOSHCOFdvg9h0LcyEeGTeLHDeNrxRUo1z+/zRU3NE8A4UDyKP/rf
|
||||
ZTVphGSNoyvo0UwyIxFQutZK6QIDAQABo1MwUTAdBgNVHQ4EFgQUCtaPQfMWTFHr
|
||||
jCUREWMGqIWYC/MwHwYDVR0jBBgwFoAUCtaPQfMWTFHrjCUREWMGqIWYC/MwDwYD
|
||||
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANgmi2fMKEFlIyKCz+4Oa
|
||||
GnOKIAACR0+yk2G+nrGxkkmT2bIiibcbYtLmfB3gQyA6O3qoi94t66QS83fIvRA5
|
||||
FHRs/wWGzKHcDCaJApxVhIN7V+480NB2W7VS2KSQh9EQQ9dziXS6xftycz7LeLR0
|
||||
WYcvojLCd1tkCFFnaN0cRjqjMdfGfsONtvc5KFGoySNs3r65ckQct7e34BELMBjv
|
||||
Zd+myNNnw6i8cnHFcnJpSYajX3dJrmaMtapjORjTjlpsjF7oYOx2dANHzOJ8sVqo
|
||||
3cElK/Ou88S/EHX/xOXx/jncz2OPGGL/8UEDh9Z2Co4/p1TYaxMHOBTlWlqjERWZ
|
||||
AmA9v9lU6gn+o5ITz0wm3M+StxF3DU9nCu3/FCezObNPM1sILRZoYmx8Ok85G/0l
|
||||
DeYgxE4je4CCh1rxyRZClRljbimE/FBbw/Ui6zrHiTAABfwVirY+wyDvPMiYhGmt
|
||||
sOfEveXVn5aX7sW1iOChZP3p2KuL5a19T5sl7+mev6Z1sea3IovYeXKsRFEVhTUU
|
||||
7533qt9lknpip9I27BT8aJKWUGP3GWXDs/DVoSkygnvbDDJfLK5Wqp8EPZCLyS1K
|
||||
L/lWamgel5BgCwb091P2rNT7Lu+XGhJOcfyFk1FAwiZ30yi3As4W9HXvdh3CNm5x
|
||||
55g7Z6Xmxn6xdtviPZmBPZU=
|
||||
-----END CERTIFICATE-----
|
52
tests/assets/client-certificates/server/server_key.pem
Normal file
52
tests/assets/client-certificates/server/server_key.pem
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
m3Ea0Y3lf0rJCBJ10pFBP3z8orJyWkI=
|
||||
-----END PRIVATE KEY-----
|
264
tests/library/client-certificates.spec.ts
Normal file
264
tests/library/client-certificates.spec.ts
Normal file
@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import { expect, playwrightTest as base } from '../config/browserTest';
|
||||
import type net from 'net';
|
||||
import type { BrowserContextOptions } from 'packages/playwright-test';
|
||||
const { createHttpsServer } = require('../../packages/playwright-core/lib/utils');
|
||||
|
||||
const test = base.extend<{ serverURL: string, serverURLRewrittenToLocalhost: string }>({
|
||||
serverURL: async ({ asset }, use) => {
|
||||
const server = createHttpsServer({
|
||||
key: fs.readFileSync(asset('client-certificates/server/server_key.pem')),
|
||||
cert: fs.readFileSync(asset('client-certificates/server/server_cert.pem')),
|
||||
ca: [
|
||||
fs.readFileSync(asset('client-certificates/server/server_cert.pem')),
|
||||
],
|
||||
requestCert: true,
|
||||
rejectUnauthorized: false,
|
||||
}, (req, res) => {
|
||||
const cert = (req.socket as import('tls').TLSSocket).getPeerCertificate();
|
||||
if ((req as any).client.authorized) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end(`Hello ${cert.subject.CN}, your certificate was issued by ${cert.issuer.CN}!`);
|
||||
} else if (cert.subject) {
|
||||
res.writeHead(403, { 'Content-Type': 'text/html' });
|
||||
res.end(`Sorry ${cert.subject.CN}, certificates from ${cert.issuer.CN} are not welcome here.`);
|
||||
} else {
|
||||
res.writeHead(401, { 'Content-Type': 'text/html' });
|
||||
res.end(`Sorry, but you need to provide a client certificate to continue.`);
|
||||
}
|
||||
});
|
||||
process.env.PWTEST_UNSUPPORTED_CUSTOM_CA = asset('client-certificates/server/server_cert.pem');
|
||||
await new Promise<void>(f => server.listen(0, 'localhost', () => f()));
|
||||
await use(`https://localhost:${(server.address() as net.AddressInfo).port}/`);
|
||||
await new Promise<void>(resolve => server.close(() => resolve()));
|
||||
},
|
||||
serverURLRewrittenToLocalhost: async ({ serverURL, browserName }, use) => {
|
||||
const parsed = new URL(serverURL);
|
||||
parsed.hostname = 'local.playwright';
|
||||
const shouldRewriteToLocalhost = browserName === 'webkit' && process.platform === 'darwin';
|
||||
await use(shouldRewriteToLocalhost ? parsed.toString() : serverURL);
|
||||
}
|
||||
});
|
||||
|
||||
test.skip(({ mode }) => mode !== 'default');
|
||||
|
||||
const kDummyFileName = __filename;
|
||||
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: [{
|
||||
certPath: kDummyFileName,
|
||||
keyPath: kDummyFileName,
|
||||
pfxPath: kDummyFileName,
|
||||
passphrase: kDummyFileName,
|
||||
}]
|
||||
}]
|
||||
}, 'pfx is specified together with cert, key or passphrase'],
|
||||
[{
|
||||
proxy: { server: 'http://localhost:8080' },
|
||||
clientCertificates: [{
|
||||
url: 'test',
|
||||
certs: [{
|
||||
certPath: kDummyFileName,
|
||||
keyPath: kDummyFileName,
|
||||
}]
|
||||
}]
|
||||
}, 'Cannot specify both proxy and clientCertificates'],
|
||||
];
|
||||
|
||||
test.describe('fetch', () => {
|
||||
test('validate input', async ({ playwright }) => {
|
||||
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 }) => {
|
||||
const request = await playwright.request.newContext();
|
||||
const response = await request.get(serverURL);
|
||||
expect(response.status()).toBe(401);
|
||||
expect(await response.text()).toBe('Sorry, but you need to provide a client certificate to continue.');
|
||||
await request.dispose();
|
||||
});
|
||||
|
||||
test('should keep supporting http', async ({ playwright, server, asset }) => {
|
||||
const request = await playwright.request.newContext({
|
||||
clientCertificates: [{
|
||||
url: server.PREFIX,
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
const response = await request.get(server.PREFIX + '/one-style.html');
|
||||
expect(response.url()).toBe(server.PREFIX + '/one-style.html');
|
||||
expect(response.status()).toBe(200);
|
||||
expect(await response.text()).toContain('<div>hello, world!</div>');
|
||||
await request.dispose();
|
||||
});
|
||||
|
||||
test('should throw with untrusted client certs', async ({ playwright, serverURL, asset }) => {
|
||||
const request = await playwright.request.newContext({
|
||||
clientCertificates: [{
|
||||
url: serverURL,
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/self-signed/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/self-signed/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
const response = await request.get(serverURL);
|
||||
expect(response.url()).toBe(serverURL);
|
||||
expect(response.status()).toBe(403);
|
||||
expect(await response.text()).toBe('Sorry Bob, certificates from Bob are not welcome here.');
|
||||
await request.dispose();
|
||||
});
|
||||
|
||||
test('pass with trusted client certificates', async ({ playwright, serverURL, asset }) => {
|
||||
const request = await playwright.request.newContext({
|
||||
clientCertificates: [{
|
||||
url: serverURL,
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
const response = await request.get(serverURL);
|
||||
expect(response.url()).toBe(serverURL);
|
||||
expect(response.status()).toBe(200);
|
||||
expect(await response.text()).toBe('Hello Alice, your certificate was issued by localhost!');
|
||||
await request.dispose();
|
||||
});
|
||||
|
||||
test('should work in the browser with request interception', async ({ browser, playwright, serverURL, asset }) => {
|
||||
const request = await playwright.request.newContext({
|
||||
clientCertificates: [{
|
||||
url: serverURL,
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
const page = await browser.newPage({ ignoreHTTPSErrors: true });
|
||||
await page.route('**/*', async route => {
|
||||
const response = await request.fetch(route.request());
|
||||
await route.fulfill({ response });
|
||||
});
|
||||
await page.goto(serverURL);
|
||||
await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible();
|
||||
await page.close();
|
||||
await request.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test.describe('browser', () => {
|
||||
test('validate input', async ({ browser }) => {
|
||||
for (const [contextOptions, expected] of kValidationSubTests)
|
||||
await expect(browser.newContext(contextOptions)).rejects.toThrow(expected);
|
||||
});
|
||||
|
||||
test('should keep supporting http', async ({ browser, server, asset }) => {
|
||||
const page = await browser.newPage({
|
||||
clientCertificates: [{
|
||||
url: server.PREFIX,
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
await page.goto(server.PREFIX + '/one-style.html');
|
||||
await expect(page.getByText('hello, world!')).toBeVisible();
|
||||
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should fail with no client certificates', async ({ browser, serverURLRewrittenToLocalhost, asset }) => {
|
||||
const page = await browser.newPage({
|
||||
clientCertificates: [{
|
||||
url: 'https://not-matching.com',
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
await page.goto(serverURLRewrittenToLocalhost);
|
||||
await expect(page.getByText('Sorry, but you need to provide a client certificate to continue.')).toBeVisible();
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should fail with self-signed client certificates', async ({ browser, serverURLRewrittenToLocalhost, asset }) => {
|
||||
const page = await browser.newPage({
|
||||
clientCertificates: [{
|
||||
url: serverURLRewrittenToLocalhost,
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/self-signed/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/self-signed/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
await page.goto(serverURLRewrittenToLocalhost);
|
||||
await expect(page.getByText('Sorry Bob, certificates from Bob are not welcome here')).toBeVisible();
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should pass with matching certificates', async ({ browser, serverURLRewrittenToLocalhost, asset }) => {
|
||||
const page = await browser.newPage({
|
||||
clientCertificates: [{
|
||||
url: serverURLRewrittenToLocalhost,
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
await page.goto(serverURLRewrittenToLocalhost);
|
||||
await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible();
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test.describe('persistentContext', () => {
|
||||
test('validate input', async ({ launchPersistent }) => {
|
||||
test.slow();
|
||||
for (const [contextOptions, expected] of kValidationSubTests)
|
||||
await expect(launchPersistent(contextOptions)).rejects.toThrow(expected);
|
||||
});
|
||||
|
||||
test('should pass with matching certificates', async ({ launchPersistent, serverURLRewrittenToLocalhost, asset }) => {
|
||||
const { page } = await launchPersistent({
|
||||
clientCertificates: [{
|
||||
url: serverURLRewrittenToLocalhost,
|
||||
certs: [{
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
}],
|
||||
});
|
||||
await page.goto(serverURLRewrittenToLocalhost);
|
||||
await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
2
utils/generate_types/overrides-test.d.ts
vendored
2
utils/generate_types/overrides-test.d.ts
vendored
@ -152,6 +152,7 @@ export type Fixtures<T extends KeyValue = {}, W extends KeyValue = {}, PT extend
|
||||
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||
type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>;
|
||||
type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
|
||||
type ClientCertificate = Exclude<BrowserContextOptions['clientCertificates'], undefined>[0];
|
||||
type ExtraHTTPHeaders = Exclude<BrowserContextOptions['extraHTTPHeaders'], undefined>;
|
||||
type Proxy = Exclude<BrowserContextOptions['proxy'], undefined>;
|
||||
type StorageState = Exclude<BrowserContextOptions['storageState'], undefined>;
|
||||
@ -209,6 +210,7 @@ export interface PlaywrightTestOptions {
|
||||
acceptDownloads: boolean;
|
||||
bypassCSP: boolean;
|
||||
colorScheme: ColorScheme;
|
||||
clientCertificates: ClientCertificate[] | undefined;
|
||||
deviceScaleFactor: number | undefined;
|
||||
extraHTTPHeaders: ExtraHTTPHeaders | undefined;
|
||||
geolocation: Geolocation | undefined;
|
||||
|
Loading…
Reference in New Issue
Block a user