-
Notifications
You must be signed in to change notification settings - Fork 3
feat: add TransportOptions for configuring TLS, proxy, and default headers #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 28 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
a2ec0f4
Add TransportOptions for configuring TLS, proxy, and default headers
mridang 378d94b
Fix hostname verification for custom CA certificates
mridang 50142f6
Fix contradictory README example showing insecure with ca_cert_path
mridang 5371a30
Make default_headers truly immutable in TransportOptions
mridang dddd8a1
Apply transport options to OAuth token exchange requests
mridang 2eefe52
Fix custom CA cert test and apply transport options to token exchange
mridang 078e4b6
Standardize transport options tests across SDKs\n\nUse testcontainers…
mridang e821828
Copy default_headers to mutable dict before assigning to config
mridang 9a5dc7d
Centralize OAuth session kwargs in TransportOptions
mridang 367dc2e
Verify default headers on API calls via WireMock verification
mridang 3c6cd4f
Remove individual transport params from factory methods
mridang c355686
Use with_access_token for proxy test reliability
mridang 6b62bec
Fix HttpWaitStrategy import for testcontainers 3.7.1
mridang c51d432
Align README Advanced Configuration with canonical structure
mridang 759c9a9
Add real proxy container to transport options test
mridang f462149
chore: align docs and remove inline comments
mridang 5709d1b
style: fix formatting
mridang e29aa2e
fix: add docker to dev dependencies
mridang 0fdaa34
merge: resolve conflicts from beta
mridang 59e769e
replace tinyproxy with ubuntu/squid:6.10-24.10_beta
mridang bc9301f
Update zitadel_client/auth/web_token_authenticator.py
mridang d7a506a
use unique network name to avoid collisions
mridang e2af097
use testcontainers Network for auto-managed network lifecycle
mridang c7d2f1b
docs: fix proxy auth docs to use URL credentials instead of default h…
mridang 231dafc
fix: add proxy container wait strategy to prevent flaky tests
mridang da26daf
Add missing param docstrings for transport_options
mridang 0dd6baa
Standardize :param transport_options: descriptions
mridang 46cf8a5
Add docstring to defaults factory method\n\nDocument the defaults() c…
mridang 3b35cb7
Fix docstring inaccuracies in transport options\n\nClarify default_he…
mridang 920c7ac
Standardize WireMock version to 3.12.1\n\nAlign transport options tes…
mridang 1f68ae2
Fix stale JWTAuthenticator references in docstrings\n\nReplace with t…
mridang 6adf93f
Use TransportOptions.defaults() consistently in factory methods
mridang 2058538
Replace programmatic WireMock stubs with static JSON mapping files\n\…
mridang 4ae3329
Standardize TransportOptions docstrings for cross-SDK consistency
mridang de2344c
Restructure tests: split TransportOptions unit tests from Zitadel int…
mridang cdaef2e
Remove unused urllib import in open_id.py
mridang a80b422
Harden insecure precedence test with nonexistent CA cert path
mridang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| -----BEGIN CERTIFICATE----- | ||
| MIIDOjCCAiKgAwIBAgIUYtCHt3J95fUpagYaFNw8M1/oV7kwDQYJKoZIhvcNAQEL | ||
| BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MDMwNDA1MTcwNVoYDzIxMjYw | ||
| MjA4MDUxNzA1WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB | ||
| AQUAA4IBDwAwggEKAoIBAQCVY1jORnqyVB9tUgYYo9U3uYCVtCzWt3lGCoxDpxAb | ||
| LlpNnqOxG33ugRbNTY/QBht37Q37PjBahMJxkRE7EPsqi2Bz2fsZMyB7pJgP5iTA | ||
| 0cILFyFzGpgUkXjmtsozKy0jAHpnzHGALjtzoKgp4SxCrWSp/MYtfMkBP9xbEpf1 | ||
| IYYQyyiISgic0/vO+nUEjyR/ULFP+nd48KjOHwWIHqwMY3nuzqScshAsyIZzSRT0 | ||
| ND2TLK1rxGoITqsOg2yTxRWwP0khvE08Y/59BGfWZq0svBCp2E3sIXg2Z3hlie7o | ||
| n+3P0F00kQfrEvkTi/cHv2vuhJpnlHxmTgJBRwhWE2+xAgMBAAGjgYEwfzAdBgNV | ||
| HQ4EFgQUPOzmGXHMRu3zIZqKLad8EkHkZvowHwYDVR0jBBgwFoAUPOzmGXHMRu3z | ||
| IZqKLad8EkHkZvowDwYDVR0TAQH/BAUwAwEB/zAsBgNVHREEJTAjgglsb2NhbGhv | ||
| c3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBAD/z | ||
| IRzYSBp6qPrvVgIX5/mEwN6ylp1J1pTC8nPQRozg0X2SEnRxz1DGBa1l046QVew2 | ||
| 3+LuGYWtVkTzEtiX7BN2jSshX8d8Ss73+psZOye6t8VcAmEeVVdnqU+EzVAhM1DP | ||
| mUiNxJPHgK2cZkpV2BHB0Ccu7qVfaIFvTk2OdbGOsQ7+r2l562kUDzCFvBo/mskO | ||
| xiIt3YMZrpyLJJzvgi+fIo351oqLvTKOHw30FelAPIHo/A2OgngsM31HvwxROYlr | ||
| C5mET6wnOtjTQbKORADTGQ8D3sJCjQJ/AI34Q4C2q/PBljVL8JKoAPzwviYAuqdd | ||
| NIIKpaYUzng24gw7+50= | ||
| -----END CERTIFICATE----- |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| http_port 3128 | ||
| acl all src all | ||
| http_access allow all |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| import json | ||
| import os | ||
| import unittest | ||
| import urllib.request | ||
| from typing import Optional | ||
|
|
||
| from testcontainers.core.container import DockerContainer | ||
| from testcontainers.core.network import Network | ||
| from testcontainers.core.wait_strategies import PortWaitStrategy | ||
| from testcontainers.core.waiting_utils import wait_container_is_ready | ||
|
|
||
| from zitadel_client.transport_options import TransportOptions | ||
| from zitadel_client.zitadel import Zitadel | ||
|
|
||
| FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixtures") | ||
|
|
||
|
|
||
| @wait_container_is_ready() | ||
| def _wait_for_wiremock(host: str, port: str) -> None: | ||
| url = f"http://{host}:{port}/__admin/mappings" | ||
| with urllib.request.urlopen(url, timeout=5) as resp: # noqa: S310 | ||
| if resp.status != 200: | ||
| raise ConnectionError(f"WireMock not ready: {resp.status}") | ||
|
|
||
|
|
||
| class TransportOptionsTest(unittest.TestCase): | ||
| host: Optional[str] = None | ||
| http_port: Optional[str] = None | ||
| https_port: Optional[str] = None | ||
| proxy_port: Optional[str] = None | ||
| ca_cert_path: Optional[str] = None | ||
| wiremock: DockerContainer = None | ||
| proxy: DockerContainer = None | ||
| network: Network = None | ||
|
|
||
| @classmethod | ||
| def setup_class(cls) -> None: | ||
| cls.ca_cert_path = os.path.join(FIXTURES_DIR, "ca.pem") | ||
| keystore_path = os.path.join(FIXTURES_DIR, "keystore.p12") | ||
| squid_conf = os.path.join(FIXTURES_DIR, "squid.conf") | ||
|
|
||
| cls.network = Network().create() | ||
|
|
||
| cls.wiremock = ( | ||
| DockerContainer("wiremock/wiremock:3.3.1") | ||
| .with_network(cls.network) | ||
| .with_network_aliases("wiremock") | ||
| .with_exposed_ports(8080, 8443) | ||
| .with_volume_mapping(keystore_path, "/home/wiremock/keystore.p12", mode="ro") | ||
| .with_command( | ||
| "--https-port 8443" | ||
| " --https-keystore /home/wiremock/keystore.p12" | ||
| " --keystore-password password" | ||
| " --keystore-type PKCS12" | ||
| " --global-response-templating" | ||
| ) | ||
| ) | ||
| cls.wiremock.start() | ||
|
|
||
| cls.proxy = ( | ||
| DockerContainer("ubuntu/squid:6.10-24.10_beta") | ||
| .with_network(cls.network) | ||
| .with_exposed_ports(3128) | ||
| .with_volume_mapping(squid_conf, "/etc/squid/squid.conf", mode="ro") | ||
| .waiting_for(PortWaitStrategy(3128)) | ||
| ) | ||
| cls.proxy.start() | ||
|
|
||
| cls.host = cls.wiremock.get_container_host_ip() | ||
| cls.http_port = cls.wiremock.get_exposed_port(8080) | ||
| cls.https_port = cls.wiremock.get_exposed_port(8443) | ||
| cls.proxy_port = cls.proxy.get_exposed_port(3128) | ||
|
|
||
| _wait_for_wiremock(cls.host, cls.http_port) | ||
|
|
||
| oidc_stub = json.dumps( | ||
| { | ||
| "request": {"method": "GET", "url": "/.well-known/openid-configuration"}, | ||
| "response": { | ||
| "status": 200, | ||
| "headers": {"Content-Type": "application/json"}, | ||
| "body": ( | ||
| '{"issuer":"{{request.baseUrl}}",' | ||
| '"token_endpoint":"{{request.baseUrl}}/oauth/v2/token",' | ||
| '"authorization_endpoint":"{{request.baseUrl}}/oauth/v2/authorize",' | ||
| '"userinfo_endpoint":"{{request.baseUrl}}/oidc/v1/userinfo",' | ||
| '"jwks_uri":"{{request.baseUrl}}/oauth/v2/keys"}' | ||
| ), | ||
| }, | ||
| } | ||
| ).encode() | ||
|
|
||
| req = urllib.request.Request( | ||
| f"http://{cls.host}:{cls.http_port}/__admin/mappings", | ||
| data=oidc_stub, | ||
| headers={"Content-Type": "application/json"}, | ||
| method="POST", | ||
| ) | ||
| with urllib.request.urlopen(req) as resp: # noqa: S310 | ||
| assert resp.status == 201 | ||
|
mridang marked this conversation as resolved.
Outdated
|
||
|
|
||
| token_stub = json.dumps( | ||
| { | ||
| "request": {"method": "POST", "url": "/oauth/v2/token"}, | ||
| "response": { | ||
| "status": 200, | ||
| "headers": {"Content-Type": "application/json"}, | ||
| "jsonBody": { | ||
| "access_token": "test-token-12345", | ||
| "token_type": "Bearer", | ||
| "expires_in": 3600, | ||
| }, | ||
| }, | ||
| } | ||
| ).encode() | ||
|
|
||
| req = urllib.request.Request( | ||
| f"http://{cls.host}:{cls.http_port}/__admin/mappings", | ||
| data=token_stub, | ||
| headers={"Content-Type": "application/json"}, | ||
| method="POST", | ||
| ) | ||
| with urllib.request.urlopen(req) as resp: # noqa: S310 | ||
| assert resp.status == 201 | ||
|
mridang marked this conversation as resolved.
Outdated
|
||
|
|
||
| settings_stub = json.dumps( | ||
| { | ||
| "request": { | ||
| "method": "POST", | ||
| "url": "/zitadel.settings.v2.SettingsService/GetGeneralSettings", | ||
| }, | ||
| "response": { | ||
| "status": 200, | ||
| "headers": {"Content-Type": "application/json"}, | ||
| "jsonBody": {}, | ||
| }, | ||
| } | ||
| ).encode() | ||
|
|
||
| req = urllib.request.Request( | ||
| f"http://{cls.host}:{cls.http_port}/__admin/mappings", | ||
| data=settings_stub, | ||
| headers={"Content-Type": "application/json"}, | ||
| method="POST", | ||
| ) | ||
| with urllib.request.urlopen(req) as resp: # noqa: S310 | ||
| assert resp.status == 201 | ||
|
mridang marked this conversation as resolved.
Outdated
|
||
|
|
||
| @classmethod | ||
| def teardown_class(cls) -> None: | ||
| if cls.proxy is not None: | ||
| cls.proxy.stop() | ||
| if cls.wiremock is not None: | ||
| cls.wiremock.stop() | ||
| if cls.network is not None: | ||
| cls.network.remove() | ||
|
mridang marked this conversation as resolved.
Outdated
|
||
|
|
||
| def test_custom_ca_cert(self) -> None: | ||
| zitadel = Zitadel.with_client_credentials( | ||
| f"https://{self.host}:{self.https_port}", | ||
| "dummy-client", | ||
| "dummy-secret", | ||
| transport_options=TransportOptions(ca_cert_path=self.ca_cert_path), | ||
| ) | ||
| self.assertIsNotNone(zitadel) | ||
|
|
||
| def test_insecure_mode(self) -> None: | ||
| zitadel = Zitadel.with_client_credentials( | ||
| f"https://{self.host}:{self.https_port}", | ||
| "dummy-client", | ||
| "dummy-secret", | ||
| transport_options=TransportOptions(insecure=True), | ||
| ) | ||
| self.assertIsNotNone(zitadel) | ||
|
|
||
| def test_default_headers(self) -> None: | ||
| zitadel = Zitadel.with_client_credentials( | ||
| f"http://{self.host}:{self.http_port}", | ||
| "dummy-client", | ||
| "dummy-secret", | ||
| transport_options=TransportOptions(default_headers={"X-Custom-Header": "test-value"}), | ||
| ) | ||
| self.assertIsNotNone(zitadel) | ||
|
|
||
| zitadel.settings.get_general_settings({}) | ||
|
|
||
| verify_body = json.dumps( | ||
| { | ||
| "url": "/zitadel.settings.v2.SettingsService/GetGeneralSettings", | ||
| "headers": {"X-Custom-Header": {"equalTo": "test-value"}}, | ||
| } | ||
| ).encode() | ||
| req = urllib.request.Request( | ||
| f"http://{self.host}:{self.http_port}/__admin/requests/count", | ||
| data=verify_body, | ||
| headers={"Content-Type": "application/json"}, | ||
| method="POST", | ||
| ) | ||
| with urllib.request.urlopen(req) as resp: # noqa: S310 | ||
| result = json.loads(resp.read().decode()) | ||
| self.assertGreaterEqual(result["count"], 1, "Custom header should be present on API call") | ||
|
|
||
| def test_proxy_url(self) -> None: | ||
| zitadel = Zitadel.with_access_token( | ||
| "http://wiremock:8080", | ||
| "test-token", | ||
| transport_options=TransportOptions(proxy_url=f"http://{self.host}:{self.proxy_port}"), | ||
| ) | ||
| self.assertIsNotNone(zitadel) | ||
| zitadel.settings.get_general_settings({}) | ||
|
|
||
| def test_no_ca_cert_fails(self) -> None: | ||
| with self.assertRaises(Exception): # noqa: B017 | ||
| Zitadel.with_client_credentials( | ||
| f"https://{self.host}:{self.https_port}", | ||
| "dummy-client", | ||
| "dummy-secret", | ||
| ) | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.