test: client-certificate follow-ups (#2508)
This commit is contained in:
Родитель
65658108c6
Коммит
86c0191b67
|
@ -206,7 +206,6 @@ class ElementHandle(JSHandle):
|
|||
"setInputFiles",
|
||||
{
|
||||
"timeout": timeout,
|
||||
"noWaitAfter": noWaitAfter,
|
||||
**converted,
|
||||
},
|
||||
)
|
||||
|
@ -246,7 +245,6 @@ class ElementHandle(JSHandle):
|
|||
position=position,
|
||||
timeout=timeout,
|
||||
force=force,
|
||||
noWaitAfter=noWaitAfter,
|
||||
trial=trial,
|
||||
)
|
||||
else:
|
||||
|
@ -254,7 +252,6 @@ class ElementHandle(JSHandle):
|
|||
position=position,
|
||||
timeout=timeout,
|
||||
force=force,
|
||||
noWaitAfter=noWaitAfter,
|
||||
trial=trial,
|
||||
)
|
||||
|
||||
|
|
|
@ -703,7 +703,6 @@ class Frame(ChannelOwner):
|
|||
"selector": selector,
|
||||
"strict": strict,
|
||||
"timeout": timeout,
|
||||
"noWaitAfter": noWaitAfter,
|
||||
**converted,
|
||||
},
|
||||
)
|
||||
|
@ -792,7 +791,6 @@ class Frame(ChannelOwner):
|
|||
position=position,
|
||||
timeout=timeout,
|
||||
force=force,
|
||||
noWaitAfter=noWaitAfter,
|
||||
strict=strict,
|
||||
trial=trial,
|
||||
)
|
||||
|
@ -802,7 +800,6 @@ class Frame(ChannelOwner):
|
|||
position=position,
|
||||
timeout=timeout,
|
||||
force=force,
|
||||
noWaitAfter=noWaitAfter,
|
||||
strict=strict,
|
||||
trial=trial,
|
||||
)
|
||||
|
|
|
@ -213,7 +213,7 @@ class Locator:
|
|||
noWaitAfter: bool = None,
|
||||
force: bool = None,
|
||||
) -> None:
|
||||
await self.fill("", timeout=timeout, noWaitAfter=noWaitAfter, force=force)
|
||||
await self.fill("", timeout=timeout, force=force)
|
||||
|
||||
def locator(
|
||||
self,
|
||||
|
@ -631,7 +631,7 @@ class Locator:
|
|||
timeout: float = None,
|
||||
noWaitAfter: bool = None,
|
||||
) -> None:
|
||||
await self.type(text, delay=delay, timeout=timeout, noWaitAfter=noWaitAfter)
|
||||
await self.type(text, delay=delay, timeout=timeout)
|
||||
|
||||
async def uncheck(
|
||||
self,
|
||||
|
@ -685,7 +685,6 @@ class Locator:
|
|||
position=position,
|
||||
timeout=timeout,
|
||||
force=force,
|
||||
noWaitAfter=noWaitAfter,
|
||||
trial=trial,
|
||||
)
|
||||
else:
|
||||
|
@ -693,7 +692,6 @@ class Locator:
|
|||
position=position,
|
||||
timeout=timeout,
|
||||
force=force,
|
||||
noWaitAfter=noWaitAfter,
|
||||
trial=trial,
|
||||
)
|
||||
|
||||
|
|
|
@ -1279,7 +1279,6 @@ class Page(ChannelOwner):
|
|||
position=position,
|
||||
timeout=timeout,
|
||||
force=force,
|
||||
noWaitAfter=noWaitAfter,
|
||||
strict=strict,
|
||||
trial=trial,
|
||||
)
|
||||
|
@ -1289,7 +1288,6 @@ class Page(ChannelOwner):
|
|||
position=position,
|
||||
timeout=timeout,
|
||||
force=force,
|
||||
noWaitAfter=noWaitAfter,
|
||||
strict=strict,
|
||||
trial=trial,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEyzCCArOgAwIBAgIUYps4gh4MqFYg8zqQhHYL7zYfbLkwDQYJKoZIhvcNAQEL
|
||||
BQAwDjEMMAoGA1UEAwwDQm9iMB4XDTI0MDcxOTEyNDc0MFoXDTI1MDcxOTEyNDc0
|
||||
MFowDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
|
||||
AgEA179eTsqcc1c3AOQHzCZEyYLPta2CCAscUFqcEZ9vWvjW0uzOv9TDlB33Unov
|
||||
jch4CElZOBhzTadVsbmnYKpxwyVU89WCuQKvedz4k1vu7S1YryfNbmS8PWbnQ4ds
|
||||
9NB7SgJNHZILvx9DXuWeFEyzRIo1984z4HheBzrkf791LqpYKaKziANUo8h8t0dm
|
||||
TX/boOz8cEnQNwtTC0ZX3aD0obG/UAhr/22ZGPo/E659fh4ptyYX2LrIUHGy+Eux
|
||||
nJ9Y4cTqa88Ee6K6AkDiT/AoNQNxE4X++jqLuie8j/ZYpI1Oll38GwKVOyy1msRL
|
||||
toGmISNwkMIQDGABrJlxgpP4QQAQ+08v9srzXOlkdxdr7OCP81r+ccBXiSQEe7BA
|
||||
kdJ8l98l5dprJ++GJ+SZcV4+/iGR0dKU2IdAG5HiKZIFn6ch9Ux+UMqeGaYCpkHr
|
||||
TiietHwcXgtVBlE0jFmB/HspmI/O0abK+grMmueaH7XtTI8YHnw0mUpL8+yp7mfA
|
||||
7zFusgFgyiBPXeD/NQgg8vja67k++d1VGoXm2xr+5WPQCSbgQoMkkOBMLHWJTefd
|
||||
6F4Z5M+oI0VwYbf6eQW246wJgpCHSPR0Vdijd6MAGRWKUuLfDsA9+12iGbKvwJ2e
|
||||
nJlStft2V2LZcjBfdIMbigW1aSVNN5w6m6YVrQPry3WPkWcCAwEAAaMhMB8wHQYD
|
||||
VR0OBBYEFPxKWTFQJSg4HD2qjxL0dnXX/z4qMA0GCSqGSIb3DQEBCwUAA4ICAQBz
|
||||
4H1d5eGRU9bekUvi7LbZ5CP/I6w6PL/9AlXqO3BZKxplK7fYGHd3uqyDorJEsvjV
|
||||
hxwvFlEnS0JIU3nRzhJU/h4Yaivf1WLRFwGZ4TPBjX9KFU27exFWD3rppazkWybJ
|
||||
i4WuEdP3TJMdKLcNTtXWUDroDOgPlS66u6oZ+mUyUROil+B+fgQgVDhjRc5fvRgZ
|
||||
Lng8wuejCo3ExQyxkwn2G5guyIimgHmOQghPtLO5xlc67Z4GPUZ1m4tC+BCiFO4D
|
||||
YIXl3QiIpmU7Pss39LLKMGXXAgLRqyMzqE52lsznu18v5vDLfTaRH4u/wjzULhXz
|
||||
SrV1IUJmhgEXta4EeDmPH0itgKtkbwjgCOD7drrFrJq/EnvIaJ5cpxiI1pFmYD8g
|
||||
VVD7/KT/CyT1Uz1dI8QaP/JX8XEgtMJaSkPfjPErIViN9rh9ECCNLgFyv7Y0Plar
|
||||
A6YlvdyV1Rta/BHndf5Hqz9QWNhbFCMQRGVQNEcoKwpFyjAE9SXoKJvFIK/w5WXu
|
||||
qKzIYA26QXE3p734Xu1n8QiFJIyltVHbyUlD0k06194t5a2WK+/eDeReIsk0QOI8
|
||||
FGqhyPZ7YjR5tSZTmgljtViqBO5AA23QOVFqtjOUrjXP5pTbPJel99Z/FTkqSwvB
|
||||
Rt4OX7HfuokWQDTT0TMn5jVtJyi54cH7f9MmsNJ23g==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,26 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIEUzCCAjsCAQAwDjEMMAoGA1UEAwwDQm9iMIICIjANBgkqhkiG9w0BAQEFAAOC
|
||||
Ag8AMIICCgKCAgEA179eTsqcc1c3AOQHzCZEyYLPta2CCAscUFqcEZ9vWvjW0uzO
|
||||
v9TDlB33Unovjch4CElZOBhzTadVsbmnYKpxwyVU89WCuQKvedz4k1vu7S1YryfN
|
||||
bmS8PWbnQ4ds9NB7SgJNHZILvx9DXuWeFEyzRIo1984z4HheBzrkf791LqpYKaKz
|
||||
iANUo8h8t0dmTX/boOz8cEnQNwtTC0ZX3aD0obG/UAhr/22ZGPo/E659fh4ptyYX
|
||||
2LrIUHGy+EuxnJ9Y4cTqa88Ee6K6AkDiT/AoNQNxE4X++jqLuie8j/ZYpI1Oll38
|
||||
GwKVOyy1msRLtoGmISNwkMIQDGABrJlxgpP4QQAQ+08v9srzXOlkdxdr7OCP81r+
|
||||
ccBXiSQEe7BAkdJ8l98l5dprJ++GJ+SZcV4+/iGR0dKU2IdAG5HiKZIFn6ch9Ux+
|
||||
UMqeGaYCpkHrTiietHwcXgtVBlE0jFmB/HspmI/O0abK+grMmueaH7XtTI8YHnw0
|
||||
mUpL8+yp7mfA7zFusgFgyiBPXeD/NQgg8vja67k++d1VGoXm2xr+5WPQCSbgQoMk
|
||||
kOBMLHWJTefd6F4Z5M+oI0VwYbf6eQW246wJgpCHSPR0Vdijd6MAGRWKUuLfDsA9
|
||||
+12iGbKvwJ2enJlStft2V2LZcjBfdIMbigW1aSVNN5w6m6YVrQPry3WPkWcCAwEA
|
||||
AaAAMA0GCSqGSIb3DQEBCwUAA4ICAQCb07d2IjUy1PeHCj/2k/z9FrZSo6K3c8y6
|
||||
b/u/MZ0AXPKLPDSo7UYpOJ8Z2cBiJ8jQapjTSEL8POUYqcvCmP55R6u68KmvINHo
|
||||
+Ly7pP+xPrbA4Q0WmPnz37hQn+I1he0GuEQyjZZqUln9zwp67TsWNKxKtCH+1j8M
|
||||
Ltzx6kuHCdPtDUtv291yhVRqvbjiDs+gzdQYNJtAkUbHwHFxu8oZhg8QZGyXYMN8
|
||||
TGoQ1LTezFZXJtX69K7WnrDGrjsgB6EMvwkqAFSYNH0LFvI0xo13OOgXr9mrwohA
|
||||
76uZtjXL9B15EqrMce6mdUZi46QJuQ2avTi57Lz+fqvsBYdQO89VcFSmqu2nfspN
|
||||
QZDrooyjHrlls8MpoBd8fde9oT4uA4/d9SJtuHUnjgGN7Qr7eTruWXL8wVMwFnvL
|
||||
igWE4detO9y2gpRLq6uEqzWYMGtN9PXJCGU8C8m9E2EBUKMrT/bpNbboatLcgRrW
|
||||
acj0BRVqoVzk1sRq7Sa6ejywqgARvIhTehg6DqdMdcENCPQ7rxDRu5PSDM8/mwIj
|
||||
0KYl8d2PlECB4ofRyLcy17BZzjP6hSnkGzcFk0/bChZOSIRnwvKbvfXnB45hhPk8
|
||||
XwT/6UNSwC2STP3gtOmLqrWj+OE0gy0AkDMvP3UnQVGMUvgfYg+N4ROCVtlqzxe9
|
||||
W65c05Mm1g==
|
||||
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
dg6m3MAyZ1IXjHlogOdmGvnq07/w
|
||||
-----END PRIVATE KEY-----
|
|
@ -15,16 +15,20 @@
|
|||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Dict, Generator, cast
|
||||
from typing import Dict, Generator, Optional, cast
|
||||
|
||||
import OpenSSL.crypto
|
||||
import OpenSSL.SSL
|
||||
import pytest
|
||||
from twisted.internet import reactor as _twisted_reactor
|
||||
from twisted.internet import ssl
|
||||
from twisted.internet.selectreactor import SelectReactor
|
||||
from twisted.web import resource, server
|
||||
from twisted.web.http import Request
|
||||
|
||||
from playwright.async_api import Browser, BrowserType, Playwright, Request, expect
|
||||
from playwright.async_api import Browser, BrowserType, Playwright, expect
|
||||
|
||||
ssl.optionsForClientTLS
|
||||
reactor = cast(SelectReactor, _twisted_reactor)
|
||||
|
||||
|
||||
|
@ -34,17 +38,61 @@ def _skip_webkit_darwin(browser_name: str) -> None:
|
|||
pytest.skip("WebKit does not proxy localhost on macOS")
|
||||
|
||||
|
||||
class Simple(resource.Resource):
|
||||
class HttpsResource(resource.Resource):
|
||||
serverCertificate: ssl.PrivateCertificate
|
||||
isLeaf = True
|
||||
|
||||
def _verify_cert_chain(self, cert: Optional[OpenSSL.crypto.X509]) -> bool:
|
||||
if not cert:
|
||||
return False
|
||||
store = OpenSSL.crypto.X509Store()
|
||||
store.add_cert(self.serverCertificate.original)
|
||||
store_ctx = OpenSSL.crypto.X509StoreContext(store, cert)
|
||||
try:
|
||||
store_ctx.verify_certificate()
|
||||
return True
|
||||
except OpenSSL.crypto.X509StoreContextError:
|
||||
return False
|
||||
|
||||
def render_GET(self, request: Request) -> bytes:
|
||||
return b"<html>Hello, world!</html>"
|
||||
tls_socket: OpenSSL.SSL.Connection = request.transport.getHandle() # type: ignore
|
||||
cert = tls_socket.get_peer_certificate()
|
||||
parts = []
|
||||
|
||||
if self._verify_cert_chain(cert):
|
||||
request.setResponseCode(200)
|
||||
parts.append(
|
||||
{
|
||||
"key": "message",
|
||||
"value": f"Hello {cert.get_subject().CN}, your certificate was issued by {cert.get_issuer().CN}!", # type: ignore
|
||||
}
|
||||
)
|
||||
elif cert and cert.get_subject():
|
||||
request.setResponseCode(403)
|
||||
parts.append(
|
||||
{
|
||||
"key": "message",
|
||||
"value": f"Sorry {cert.get_subject().CN}, certificates from {cert.get_issuer().CN} are not welcome here.",
|
||||
}
|
||||
)
|
||||
else:
|
||||
request.setResponseCode(401)
|
||||
parts.append(
|
||||
{
|
||||
"key": "message",
|
||||
"value": "Sorry, but you need to provide a client certificate to continue.",
|
||||
}
|
||||
)
|
||||
return b"".join(
|
||||
[
|
||||
f'<div data-testid="{part["key"]}">{part["value"]}</div>'.encode()
|
||||
for part in parts
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def _client_certificate_server(assetdir: Path) -> Generator[None, None, None]:
|
||||
server.Site(Simple())
|
||||
|
||||
certAuthCert = ssl.Certificate.loadPEM(
|
||||
(assetdir / "client-certificates/server/server_cert.pem").read_text()
|
||||
)
|
||||
|
@ -54,7 +102,10 @@ def _client_certificate_server(assetdir: Path) -> Generator[None, None, None]:
|
|||
)
|
||||
|
||||
contextFactory = serverCert.options(certAuthCert)
|
||||
site = server.Site(Simple())
|
||||
contextFactory.requireCertificate = False
|
||||
resource = HttpsResource()
|
||||
resource.serverCertificate = serverCert
|
||||
site = server.Site(resource)
|
||||
|
||||
def _run() -> None:
|
||||
reactor.listenSSL(8000, site, contextFactory)
|
||||
|
@ -65,6 +116,27 @@ def _client_certificate_server(assetdir: Path) -> Generator[None, None, None]:
|
|||
thread.join()
|
||||
|
||||
|
||||
async def test_should_throw_with_untrusted_client_certs(
|
||||
playwright: Playwright, assetdir: Path
|
||||
) -> None:
|
||||
serverURL = "https://localhost:8000/"
|
||||
request = await playwright.request.new_context(
|
||||
# TODO: Remove this once we can pass a custom CA.
|
||||
ignore_https_errors=True,
|
||||
client_certificates=[
|
||||
{
|
||||
"origin": serverURL,
|
||||
"certPath": assetdir
|
||||
/ "client-certificates/client/self-signed/cert.pem",
|
||||
"keyPath": assetdir / "client-certificates/client/self-signed/key.pem",
|
||||
}
|
||||
],
|
||||
)
|
||||
with pytest.raises(Exception, match="alert unknown ca"):
|
||||
await request.get(serverURL)
|
||||
await request.dispose()
|
||||
|
||||
|
||||
async def test_should_work_with_new_context(browser: Browser, assetdir: Path) -> None:
|
||||
context = await browser.new_context(
|
||||
# TODO: Remove this once we can pass a custom CA.
|
||||
|
@ -79,14 +151,24 @@ async def test_should_work_with_new_context(browser: Browser, assetdir: Path) ->
|
|||
)
|
||||
page = await context.new_page()
|
||||
await page.goto("https://localhost:8000")
|
||||
await expect(page.get_by_text("alert certificate required")).to_be_visible()
|
||||
await expect(page.get_by_test_id("message")).to_have_text(
|
||||
"Sorry, but you need to provide a client certificate to continue."
|
||||
)
|
||||
await page.goto("https://127.0.0.1:8000")
|
||||
await expect(page.get_by_text("Hello, world!")).to_be_visible()
|
||||
await expect(page.get_by_test_id("message")).to_have_text(
|
||||
"Hello Alice, your certificate was issued by localhost!"
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="alert certificate required"):
|
||||
await page.context.request.get("https://localhost:8000")
|
||||
response = await page.context.request.get("https://localhost:8000")
|
||||
assert (
|
||||
"Sorry, but you need to provide a client certificate to continue."
|
||||
in await response.text()
|
||||
)
|
||||
response = await page.context.request.get("https://127.0.0.1:8000")
|
||||
assert "Hello, world!" in await response.text()
|
||||
assert (
|
||||
"Hello Alice, your certificate was issued by localhost!"
|
||||
in await response.text()
|
||||
)
|
||||
await context.close()
|
||||
|
||||
|
||||
|
@ -108,9 +190,13 @@ async def test_should_work_with_new_persistent_context(
|
|||
)
|
||||
page = await context.new_page()
|
||||
await page.goto("https://localhost:8000")
|
||||
await expect(page.get_by_text("alert certificate required")).to_be_visible()
|
||||
await expect(page.get_by_test_id("message")).to_have_text(
|
||||
"Sorry, but you need to provide a client certificate to continue."
|
||||
)
|
||||
await page.goto("https://127.0.0.1:8000")
|
||||
await expect(page.get_by_text("Hello, world!")).to_be_visible()
|
||||
await expect(page.get_by_test_id("message")).to_have_text(
|
||||
"Hello Alice, your certificate was issued by localhost!"
|
||||
)
|
||||
await context.close()
|
||||
|
||||
|
||||
|
@ -128,8 +214,14 @@ async def test_should_work_with_global_api_request_context(
|
|||
}
|
||||
],
|
||||
)
|
||||
with pytest.raises(Exception, match="alert certificate required"):
|
||||
await request.get("https://localhost:8000")
|
||||
response = await request.get("https://localhost:8000")
|
||||
assert (
|
||||
"Sorry, but you need to provide a client certificate to continue."
|
||||
in await response.text()
|
||||
)
|
||||
response = await request.get("https://127.0.0.1:8000")
|
||||
assert "Hello, world!" in await response.text()
|
||||
assert (
|
||||
"Hello Alice, your certificate was issued by localhost!"
|
||||
in await response.text()
|
||||
)
|
||||
await request.dispose()
|
||||
|
|
|
@ -15,15 +15,18 @@
|
|||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Dict, Generator, cast
|
||||
from typing import Dict, Generator, Optional, cast
|
||||
|
||||
import OpenSSL.crypto
|
||||
import OpenSSL.SSL
|
||||
import pytest
|
||||
from twisted.internet import reactor as _twisted_reactor
|
||||
from twisted.internet import ssl
|
||||
from twisted.internet.selectreactor import SelectReactor
|
||||
from twisted.web import resource, server
|
||||
from twisted.web.http import Request
|
||||
|
||||
from playwright.sync_api import Browser, BrowserType, Playwright, Request, expect
|
||||
from playwright.sync_api import Browser, BrowserType, Playwright, expect
|
||||
|
||||
reactor = cast(SelectReactor, _twisted_reactor)
|
||||
|
||||
|
@ -34,17 +37,61 @@ def _skip_webkit_darwin(browser_name: str) -> None:
|
|||
pytest.skip("WebKit does not proxy localhost on macOS")
|
||||
|
||||
|
||||
class Simple(resource.Resource):
|
||||
class HttpsResource(resource.Resource):
|
||||
serverCertificate: ssl.PrivateCertificate
|
||||
isLeaf = True
|
||||
|
||||
def _verify_cert_chain(self, cert: Optional[OpenSSL.crypto.X509]) -> bool:
|
||||
if not cert:
|
||||
return False
|
||||
store = OpenSSL.crypto.X509Store()
|
||||
store.add_cert(self.serverCertificate.original)
|
||||
store_ctx = OpenSSL.crypto.X509StoreContext(store, cert)
|
||||
try:
|
||||
store_ctx.verify_certificate()
|
||||
return True
|
||||
except OpenSSL.crypto.X509StoreContextError:
|
||||
return False
|
||||
|
||||
def render_GET(self, request: Request) -> bytes:
|
||||
return b"<html>Hello, world!</html>"
|
||||
tls_socket: OpenSSL.SSL.Connection = request.transport.getHandle() # type: ignore
|
||||
cert = tls_socket.get_peer_certificate()
|
||||
parts = []
|
||||
|
||||
if self._verify_cert_chain(cert):
|
||||
request.setResponseCode(200)
|
||||
parts.append(
|
||||
{
|
||||
"key": "message",
|
||||
"value": f"Hello {cert.get_subject().CN}, your certificate was issued by {cert.get_issuer().CN}!", # type: ignore
|
||||
}
|
||||
)
|
||||
elif cert and cert.get_subject():
|
||||
request.setResponseCode(403)
|
||||
parts.append(
|
||||
{
|
||||
"key": "message",
|
||||
"value": f"Sorry {cert.get_subject().CN}, certificates from {cert.get_issuer().CN} are not welcome here.",
|
||||
}
|
||||
)
|
||||
else:
|
||||
request.setResponseCode(401)
|
||||
parts.append(
|
||||
{
|
||||
"key": "message",
|
||||
"value": "Sorry, but you need to provide a client certificate to continue.",
|
||||
}
|
||||
)
|
||||
return b"".join(
|
||||
[
|
||||
f'<div data-testid="{part["key"]}">{part["value"]}</div>'.encode()
|
||||
for part in parts
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def _client_certificate_server(assetdir: Path) -> Generator[None, None, None]:
|
||||
server.Site(Simple())
|
||||
|
||||
certAuthCert = ssl.Certificate.loadPEM(
|
||||
(assetdir / "client-certificates/server/server_cert.pem").read_text()
|
||||
)
|
||||
|
@ -54,7 +101,10 @@ def _client_certificate_server(assetdir: Path) -> Generator[None, None, None]:
|
|||
)
|
||||
|
||||
contextFactory = serverCert.options(certAuthCert)
|
||||
site = server.Site(Simple())
|
||||
contextFactory.requireCertificate = False
|
||||
resource = HttpsResource()
|
||||
resource.serverCertificate = serverCert
|
||||
site = server.Site(resource)
|
||||
|
||||
def _run() -> None:
|
||||
reactor.listenSSL(8000, site, contextFactory)
|
||||
|
@ -65,6 +115,27 @@ def _client_certificate_server(assetdir: Path) -> Generator[None, None, None]:
|
|||
thread.join()
|
||||
|
||||
|
||||
def test_should_throw_with_untrusted_client_certs(
|
||||
playwright: Playwright, assetdir: Path
|
||||
) -> None:
|
||||
serverURL = "https://localhost:8000/"
|
||||
request = playwright.request.new_context(
|
||||
# TODO: Remove this once we can pass a custom CA.
|
||||
ignore_https_errors=True,
|
||||
client_certificates=[
|
||||
{
|
||||
"origin": serverURL,
|
||||
"certPath": assetdir
|
||||
/ "client-certificates/client/self-signed/cert.pem",
|
||||
"keyPath": assetdir / "client-certificates/client/self-signed/key.pem",
|
||||
}
|
||||
],
|
||||
)
|
||||
with pytest.raises(Exception, match="alert unknown ca"):
|
||||
request.get(serverURL)
|
||||
request.dispose()
|
||||
|
||||
|
||||
def test_should_work_with_new_context(browser: Browser, assetdir: Path) -> None:
|
||||
context = browser.new_context(
|
||||
# TODO: Remove this once we can pass a custom CA.
|
||||
|
@ -79,14 +150,21 @@ def test_should_work_with_new_context(browser: Browser, assetdir: Path) -> None:
|
|||
)
|
||||
page = context.new_page()
|
||||
page.goto("https://localhost:8000")
|
||||
expect(page.get_by_text("alert certificate required")).to_be_visible()
|
||||
expect(page.get_by_test_id("message")).to_have_text(
|
||||
"Sorry, but you need to provide a client certificate to continue."
|
||||
)
|
||||
page.goto("https://127.0.0.1:8000")
|
||||
expect(page.get_by_text("Hello, world!")).to_be_visible()
|
||||
expect(page.get_by_test_id("message")).to_have_text(
|
||||
"Hello Alice, your certificate was issued by localhost!"
|
||||
)
|
||||
|
||||
with pytest.raises(Exception, match="alert certificate required"):
|
||||
page.context.request.get("https://localhost:8000")
|
||||
response = page.context.request.get("https://localhost:8000")
|
||||
assert (
|
||||
"Sorry, but you need to provide a client certificate to continue."
|
||||
in response.text()
|
||||
)
|
||||
response = page.context.request.get("https://127.0.0.1:8000")
|
||||
assert "Hello, world!" in response.text()
|
||||
assert "Hello Alice, your certificate was issued by localhost!" in response.text()
|
||||
context.close()
|
||||
|
||||
|
||||
|
@ -108,9 +186,13 @@ def test_should_work_with_new_persistent_context(
|
|||
)
|
||||
page = context.new_page()
|
||||
page.goto("https://localhost:8000")
|
||||
expect(page.get_by_text("alert certificate required")).to_be_visible()
|
||||
expect(page.get_by_test_id("message")).to_have_text(
|
||||
"Sorry, but you need to provide a client certificate to continue."
|
||||
)
|
||||
page.goto("https://127.0.0.1:8000")
|
||||
expect(page.get_by_text("Hello, world!")).to_be_visible()
|
||||
expect(page.get_by_test_id("message")).to_have_text(
|
||||
"Hello Alice, your certificate was issued by localhost!"
|
||||
)
|
||||
context.close()
|
||||
|
||||
|
||||
|
@ -128,8 +210,11 @@ def test_should_work_with_global_api_request_context(
|
|||
}
|
||||
],
|
||||
)
|
||||
with pytest.raises(Exception, match="alert certificate required"):
|
||||
request.get("https://localhost:8000")
|
||||
response = request.get("https://localhost:8000")
|
||||
assert (
|
||||
"Sorry, but you need to provide a client certificate to continue."
|
||||
in response.text()
|
||||
)
|
||||
response = request.get("https://127.0.0.1:8000")
|
||||
assert "Hello, world!" in response.text()
|
||||
assert "Hello Alice, your certificate was issued by localhost!" in response.text()
|
||||
request.dispose()
|
||||
|
|
Загрузка…
Ссылка в новой задаче