Skip to content

Commit 960a13d

Browse files
committed
fix(rest): keep a table-scoped token off the shared session
commit_table bound self._session.headers and wrote the table's token into it, so the token outlived the commit and was sent with later requests for other tables. Build the request headers from a copy instead; the request itself is unchanged.
1 parent 1312c55 commit 960a13d

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

‎pyiceberg/catalog/rest/__init__.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1603,7 +1603,10 @@ def commit_table(
16031603
table_identifier = TableIdentifier(namespace=identifier[:-1], name=identifier[-1])
16041604
table_request = CommitTableRequest(identifier=table_identifier, requirements=requirements, updates=updates)
16051605

1606-
headers = self._session.headers
1606+
# A table-scoped token applies to this request only, so it is layered onto a
1607+
# copy: assigning into self._session.headers would leave it on the session and
1608+
# send it with every later request, including ones for other tables.
1609+
headers = dict(self._session.headers)
16071610
if table_token := table.config.get(TOKEN):
16081611
headers[AUTHORIZATION_HEADER] = f"{BEARER_PREFIX} {table_token}"
16091612

‎tests/catalog/test_rest.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3467,3 +3467,39 @@ def test_load_table_without_storage_credentials(
34673467
)
34683468
assert actual.metadata.model_dump() == expected.metadata.model_dump()
34693469
assert actual == expected
3470+
3471+
3472+
def test_commit_table_does_not_leak_table_token_onto_session(
3473+
rest_mock: Mocker, example_table_metadata_v2: dict[str, Any]
3474+
) -> None:
3475+
"""A table-scoped token must not outlive the commit that used it.
3476+
3477+
The catalog session is shared by every table, so a token left on it would be
3478+
sent with later requests for other tables.
3479+
"""
3480+
table_token = "table_scoped_token"
3481+
metadata_location = "s3://warehouse/database/table/metadata.json"
3482+
3483+
rest_mock.get(
3484+
f"{TEST_URI}v1/namespaces/namespace/tables/table_name",
3485+
json={
3486+
"metadata-location": metadata_location,
3487+
"metadata": example_table_metadata_v2,
3488+
"config": {"token": table_token},
3489+
},
3490+
status_code=200,
3491+
request_headers=TEST_HEADERS,
3492+
)
3493+
3494+
catalog = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN)
3495+
table = catalog.load_table(("namespace", "table_name"))
3496+
3497+
rest_mock.post(
3498+
f"{TEST_URI}v1/namespaces/namespace/tables/table_name",
3499+
json={"metadata-location": metadata_location, "metadata": example_table_metadata_v2},
3500+
status_code=200,
3501+
)
3502+
3503+
table.update_schema().add_column("new_col", StringType()).commit()
3504+
3505+
assert "Authorization" not in catalog._session.headers

0 commit comments

Comments
 (0)