feat: add repository name sanitization to prevent invalid characters in poetry config

Signed-off-by: d9pouces <github@19pouces.net>
This commit is contained in:
d9pouces
2026-10-06 20:34:04 +02:00
parent 98b773445f
commit 665eb215bd
3 changed files with 53 additions and 3 deletions
+17 -2
View File
@@ -25,6 +25,7 @@ from __future__ import annotations
import json
import logging
import re
from urllib.parse import urlparse
from id_configurator.config import PythonRepository
@@ -38,12 +39,25 @@ _PIP_CONF = "etc/pip.conf"
_UV_TOML = "etc/uv/uv.toml"
_POETRY_CONFIG = "root/.config/pypoetry/config.toml"
_DISTUTILS_GLOBS = ("usr/lib/python3.*", "usr/local/lib/python3.*")
_REPOSITORY_NAME_FORBIDDEN_CHARS = re.compile(r"[^A-Za-z0-9_-]")
def _toml_string(value: str) -> str:
return json.dumps(value)
def _sanitize_repository_name(name: str) -> str:
"""Replace every character of `name` that isn't a plain identifier
character with `_`.
`name` is written verbatim as a TOML table key (`[repositories.name]`,
`[certificates.name]`) in poetry's config: an unrestricted name could
break out of the table header, or (via a literal `.`) silently create
extra, nested tables instead of naming the one table it is meant to.
"""
return _REPOSITORY_NAME_FORBIDDEN_CHARS.sub("_", name)
def _trusted_host(repository: PythonRepository) -> str | None:
parsed = urlparse(repository.index_url)
return None if parsed.scheme.lower() == "https" else parsed.hostname
@@ -83,10 +97,11 @@ def _write_poetry_config(
) -> None:
lines = []
for repository in repositories:
lines.append(f"[repositories.{repository.name}]")
name = _sanitize_repository_name(repository.name)
lines.append(f"[repositories.{name}]")
lines.append(f"url = {_toml_string(repository.index_url)}")
lines.append("")
lines.append(f"[certificates.{repository.name}]")
lines.append(f"[certificates.{name}]")
lines.append(f"cert = {_toml_string(_CA_BUNDLE)}")
lines.append("")
content = ("\n".join(lines).rstrip() + "\n").encode("utf-8")
+1 -1
View File
@@ -44,7 +44,7 @@ maintainers = [
name = "id-configurator"
readme = "README.md"
requires-python = ">=3.13"
version = "2026.10.5"
version = "2026.10.6"
[project.scripts]
id-configurator = "id_configurator:main"
+35
View File
@@ -183,6 +183,41 @@ def test_apply_multiple_repositories(tmp_path) -> None:
)
def test_apply_sanitizes_poetry_repository_name_with_special_characters(
tmp_path,
) -> None:
data = {
"uuid": "12345678-1234-5678-1234-567812345678",
"organisation": "InterDiode",
"configurations": {
"python": {
"repositories": [
{
"name": "weird.name]here[bad",
"index-url": "https://example.invalid/simple",
}
]
}
},
}
config = Configuration.model_validate(data)
dst = tmp_path / "dst"
context = Context(config=config, dst=dst, src=None)
PythonModule().apply(context)
poetry_config = (dst / "root/.config/pypoetry/config.toml").read_text(
encoding="utf-8"
)
assert poetry_config == (
"[repositories.weird_name_here_bad]\n"
'url = "https://example.invalid/simple"\n'
"\n"
"[certificates.weird_name_here_bad]\n"
'cert = "/etc/ssl/certs/ca-certificates.crt"\n'
)
def test_apply_writes_poetry_config_for_each_configured_user(tmp_path) -> None:
config = Configuration.model_validate(ONE_REPO_CONFIG)
dst = tmp_path / "dst"