Skip to content

Commit 7b153a8

Browse files
feat: configurable API-key header (api_key_header)
1 parent 721061d commit 7b153a8

5 files changed

Lines changed: 54 additions & 2 deletions

File tree

‎plane/api/base_resource.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def _build_url(self, endpoint: str) -> str:
9191
def _headers(self) -> dict[str, str]:
9292
headers: dict[str, str] = {"Content-Type": "application/json"}
9393
if self.config.api_key:
94-
headers["X-Api-Key"] = self.config.api_key
94+
headers[self.config.api_key_header] = self.config.api_key
9595
if self.config.access_token:
9696
headers["Authorization"] = f"Bearer {self.config.access_token}"
9797
return headers

‎plane/api/v2/_kernel/transport.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def request(
5353
def _headers(self) -> dict[str, str]:
5454
headers = {"Content-Type": "application/json", "Accept": "application/json"}
5555
if self.config.api_key:
56-
headers["X-Api-Key"] = self.config.api_key
56+
headers[self.config.api_key_header] = self.config.api_key
5757
if self.config.access_token:
5858
headers["Authorization"] = f"Bearer {self.config.access_token}"
5959
return headers

‎plane/client/plane_client.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(
4444
base_url: str,
4545
api_key: str | None = None,
4646
access_token: str | None = None,
47+
api_key_header: str = "X-Api-Key",
4748
) -> None:
4849
if not api_key and not access_token:
4950
raise ConfigurationError(
@@ -58,6 +59,7 @@ def __init__(
5859
base_path=base_url,
5960
api_key=api_key,
6061
access_token=access_token,
62+
api_key_header=api_key_header,
6163
)
6264

6365
# api_v2 surface. v1 resources below are unchanged.

‎plane/config.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def __init__(
2323
access_token: str | None = None,
2424
timeout: float | tuple[float, float] | None = 30.0,
2525
retry: RetryConfig | None = None,
26+
api_key_header: str = "X-Api-Key",
2627
) -> None:
2728
if not api_key and not access_token:
2829
raise ConfigurationError(
@@ -41,3 +42,8 @@ def __init__(
4142
self.access_token = access_token
4243
self.timeout = timeout
4344
self.retry = retry
45+
# Header that carries api_key. Plane reads X-Api-Key; an API gateway in front
46+
# of Plane may expect its own consumer-key header instead (e.g. X-Gravitee-Api-Key).
47+
if not api_key_header or not api_key_header.strip():
48+
raise ConfigurationError("'api_key_header' must be a non-empty header name")
49+
self.api_key_header = api_key_header.strip()

‎tests/unit/test_api_key_header.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Unit tests for the configurable API-key header (no network, no env)."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from plane.api.base_resource import BaseResource
8+
from plane.api.v2._kernel.transport import V2Transport
9+
from plane.client import PlaneClient
10+
from plane.config import Configuration
11+
from plane.errors.errors import ConfigurationError
12+
13+
14+
def test_default_header_is_x_api_key() -> None:
15+
config = Configuration(base_path="https://api.plane.so", api_key="k")
16+
headers = BaseResource(config, "/workspaces/")._headers()
17+
assert headers["X-Api-Key"] == "k"
18+
19+
20+
def test_custom_header_replaces_x_api_key_in_v1_and_v2() -> None:
21+
config = Configuration(
22+
base_path="http://gateway/plane-api", api_key="k", api_key_header="X-Gravitee-Api-Key"
23+
)
24+
for headers in (BaseResource(config, "/workspaces/")._headers(), V2Transport(config)._headers()):
25+
assert headers["X-Gravitee-Api-Key"] == "k"
26+
assert "X-Api-Key" not in headers
27+
28+
29+
def test_plane_client_passes_the_header_through() -> None:
30+
client = PlaneClient(base_url="http://gateway", api_key="k", api_key_header="X-Custom-Key")
31+
assert client.config.api_key_header == "X-Custom-Key"
32+
assert client.work_items._headers()["X-Custom-Key"] == "k"
33+
34+
35+
def test_blank_header_is_refused() -> None:
36+
with pytest.raises(ConfigurationError):
37+
Configuration(base_path="https://api.plane.so", api_key="k", api_key_header=" ")
38+
39+
40+
def test_access_token_ignores_the_header_setting() -> None:
41+
config = Configuration(base_path="https://api.plane.so", access_token="t", api_key_header="X-Other")
42+
headers = BaseResource(config, "/workspaces/")._headers()
43+
assert headers["Authorization"] == "Bearer t"
44+
assert "X-Other" not in headers

0 commit comments

Comments
 (0)