hestia-keyring 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ .coverage
10
+ htmlcov/
11
+
12
+ # uv
13
+ .venv/
14
+
15
+ # IDE
16
+ .vscode/
17
+ .idea/
18
+ *.swp
19
+
20
+ # Templates: include scaffold-emitted .vscode (they ship recommended editor settings).
21
+ !packages/hestia-templates/**/.vscode/
22
+ !packages/hestia-templates/**/.vscode/**
23
+
24
+ # OS
25
+ .DS_Store
26
+ Thumbs.db
27
+
28
+ # Local dev
29
+ .env
30
+ .env.local
31
+ .hestia/
32
+ docker-compose.override.yaml
33
+
34
+ # Parallel sub-plan worktrees
35
+ .worktrees/
36
+
37
+ # mkdocs build output
38
+ site/
39
+
40
+ # Local Claude Code skill cache (machine-specific, not project state)
41
+ .claude/
42
+ .others/
43
+
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: hestia-keyring
3
+ Version: 1.0.0
4
+ Summary: Keyring backend for HES Azure Artifacts — enables uv sync without PATs
5
+ License: MIT
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: keyring>=24.0
8
+ Description-Content-Type: text/markdown
9
+
10
+ # hestia-keyring
11
+
12
+ A [keyring](https://pypi.org/project/keyring/) backend that authenticates `uv sync` against
13
+ the HES Azure Artifacts feed using your active `az` CLI session — no Personal Access Tokens
14
+ required.
15
+
16
+ ## Install once per machine
17
+
18
+ ```bash
19
+ uv tool install keyring --with hestia-keyring
20
+ ```
21
+
22
+ ## What it does
23
+
24
+ When `uv` calls `keyring get https://pkgs.dev.azure.com/... VssSessionToken`, this backend
25
+ runs `az account get-access-token --resource https://app.vssps.visualstudio.com` and returns
26
+ the resulting bearer token. Your `az login` session is the only credential you need.
27
+
28
+ ## Requirements
29
+
30
+ - Python 3.13+
31
+ - [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) installed and
32
+ signed in (`az login`)
33
+
34
+ ## Consumer app configuration
35
+
36
+ Add to your app's `pyproject.toml`:
37
+
38
+ ```toml
39
+ [[tool.uv.index]]
40
+ name = "hes-internal"
41
+ url = "https://VssSessionToken@pkgs.dev.azure.com/hmc-heerema/HES%20Internal/_packaging/HES/pypi/simple/"
42
+ default = false
43
+ explicit = true
44
+
45
+ [tool.uv.sources]
46
+ hestia = { index = "hes-internal" }
47
+
48
+ [tool.uv]
49
+ keyring-provider = "subprocess"
50
+ ```
51
+
52
+ The `VssSessionToken@` prefix in the URL is required — uv passes the URL username to
53
+ `keyring get`, and `VssSessionToken` is the username this backend recognises.
@@ -0,0 +1,44 @@
1
+ # hestia-keyring
2
+
3
+ A [keyring](https://pypi.org/project/keyring/) backend that authenticates `uv sync` against
4
+ the HES Azure Artifacts feed using your active `az` CLI session — no Personal Access Tokens
5
+ required.
6
+
7
+ ## Install once per machine
8
+
9
+ ```bash
10
+ uv tool install keyring --with hestia-keyring
11
+ ```
12
+
13
+ ## What it does
14
+
15
+ When `uv` calls `keyring get https://pkgs.dev.azure.com/... VssSessionToken`, this backend
16
+ runs `az account get-access-token --resource https://app.vssps.visualstudio.com` and returns
17
+ the resulting bearer token. Your `az login` session is the only credential you need.
18
+
19
+ ## Requirements
20
+
21
+ - Python 3.13+
22
+ - [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) installed and
23
+ signed in (`az login`)
24
+
25
+ ## Consumer app configuration
26
+
27
+ Add to your app's `pyproject.toml`:
28
+
29
+ ```toml
30
+ [[tool.uv.index]]
31
+ name = "hes-internal"
32
+ url = "https://VssSessionToken@pkgs.dev.azure.com/hmc-heerema/HES%20Internal/_packaging/HES/pypi/simple/"
33
+ default = false
34
+ explicit = true
35
+
36
+ [tool.uv.sources]
37
+ hestia = { index = "hes-internal" }
38
+
39
+ [tool.uv]
40
+ keyring-provider = "subprocess"
41
+ ```
42
+
43
+ The `VssSessionToken@` prefix in the URL is required — uv passes the URL username to
44
+ `keyring get`, and `VssSessionToken` is the username this backend recognises.
@@ -0,0 +1,21 @@
1
+ # packages/hestia-keyring/azure-pipelines.yml
2
+ #
3
+ # CI for hestia-keyring. Triggers on:
4
+ # - Pushes to main (runs Install + Verify; no Release)
5
+ # - Tags matching hestia-keyring@X.Y.Z (runs full pipeline including PyPI publish)
6
+ #
7
+ # IMPORTANT: This pipeline publishes to PUBLIC PyPI, not ADO Artifacts.
8
+ # Do NOT change the template reference to uv-python-package.yml — that
9
+ # would publish to ADO Artifacts. This package must remain on public PyPI.
10
+
11
+ trigger:
12
+ branches: { include: [main] }
13
+ tags: { include: ['hestia-keyring@*'] }
14
+
15
+ pr:
16
+ branches: { include: [main] }
17
+
18
+ extends:
19
+ template: ../hestia-pipelines/templates/uv-python-package-pypi.yml
20
+ parameters:
21
+ packageName: hestia-keyring
@@ -0,0 +1,30 @@
1
+ [project]
2
+ name = "hestia-keyring"
3
+ version = "1.0.0"
4
+ description = "Keyring backend for HES Azure Artifacts — enables uv sync without PATs"
5
+ requires-python = ">=3.13"
6
+ readme = "README.md"
7
+ license = { text = "MIT" }
8
+ dependencies = [
9
+ "keyring>=24.0",
10
+ ]
11
+
12
+ [project.entry-points."keyring.backends"]
13
+ hestia = "hestia_keyring:HestiaArtifactsKeyring"
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
18
+
19
+ [tool.hatch.build.targets.wheel]
20
+ packages = ["src/hestia_keyring"]
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "pytest>=8.3",
25
+ "pytest-cov>=5.0",
26
+ ]
27
+
28
+ [tool.pytest.ini_options]
29
+ testpaths = ["tests"]
30
+ addopts = "-ra --strict-markers"
@@ -0,0 +1,52 @@
1
+ import shutil
2
+ import subprocess
3
+ from urllib.parse import urlparse
4
+
5
+ from keyring.backend import KeyringBackend
6
+ from keyring.credentials import SimpleCredential
7
+
8
+ _AZ = shutil.which("az") or "az"
9
+ _FEED_HOST = "pkgs.dev.azure.com"
10
+ _RESOURCE = "https://app.vssps.visualstudio.com"
11
+
12
+
13
+ class HestiaArtifactsKeyring(KeyringBackend):
14
+ """Keyring backend for HES Azure Artifacts feeds.
15
+
16
+ Uses the active az CLI session — no PATs, no tenant discovery.
17
+ Install once: uv tool install keyring --with hestia-keyring
18
+ """
19
+
20
+ priority = 9
21
+
22
+ @classmethod
23
+ def viable(cls) -> bool:
24
+ return shutil.which("az") is not None
25
+
26
+ def get_password(self, service: str, username: str) -> str | None:
27
+ cred = self.get_credential(service, username)
28
+ return cred.password if cred else None
29
+
30
+ def get_credential(self, service: str, username: str) -> SimpleCredential | None:
31
+ host = (urlparse(service).hostname or "").lower().rstrip(".")
32
+ if host != _FEED_HOST:
33
+ return None
34
+ try:
35
+ token = subprocess.check_output(
36
+ [_AZ, "account", "get-access-token",
37
+ "--resource", _RESOURCE,
38
+ "--query", "accessToken", "-o", "tsv"],
39
+ stderr=subprocess.DEVNULL,
40
+ timeout=30,
41
+ ).decode().strip()
42
+ if token:
43
+ return SimpleCredential("VssSessionToken", token)
44
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
45
+ pass
46
+ return None
47
+
48
+ def set_password(self, service: str, username: str, password: str) -> None:
49
+ raise NotImplementedError
50
+
51
+ def delete_password(self, service: str, username: str) -> None:
52
+ raise NotImplementedError
File without changes
File without changes
@@ -0,0 +1,88 @@
1
+ import subprocess
2
+ from unittest.mock import patch
3
+
4
+ import pytest
5
+
6
+ from hestia_keyring import HestiaArtifactsKeyring
7
+
8
+ _FEED = "https://pkgs.dev.azure.com/hmc-heerema/HES%20Internal/_packaging/HES/pypi/simple/"
9
+ _OTHER = "https://pypi.org/simple/"
10
+ _TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.test"
11
+
12
+
13
+ def test_priority():
14
+ assert HestiaArtifactsKeyring.priority == 9
15
+
16
+
17
+ def test_viable_true_when_az_found():
18
+ with patch("hestia_keyring.shutil.which", return_value=r"C:\az.cmd"):
19
+ assert HestiaArtifactsKeyring.viable() is True
20
+
21
+
22
+ def test_viable_false_when_az_not_found():
23
+ with patch("hestia_keyring.shutil.which", return_value=None):
24
+ assert HestiaArtifactsKeyring.viable() is False
25
+
26
+
27
+ def test_get_credential_ignores_non_feed_url():
28
+ assert HestiaArtifactsKeyring().get_credential(_OTHER, "token") is None
29
+
30
+
31
+ def test_get_credential_rejects_url_with_feed_host_in_path():
32
+ # Substring bypass: attacker URL contains pkgs.dev.azure.com in path, not hostname.
33
+ # Must be rejected — only exact hostname match is valid.
34
+ evil = "https://evil.com/pkgs.dev.azure.com/steal-token"
35
+ assert HestiaArtifactsKeyring().get_credential(evil, "VssSessionToken") is None
36
+
37
+
38
+ def test_get_credential_returns_credential_for_feed_url():
39
+ with patch("hestia_keyring.subprocess.check_output", return_value=f"{_TOKEN}\n".encode()):
40
+ cred = HestiaArtifactsKeyring().get_credential(_FEED, "VssSessionToken")
41
+ assert cred is not None
42
+ assert cred.username == "VssSessionToken"
43
+ assert cred.password == _TOKEN
44
+
45
+
46
+ def test_get_credential_returns_none_on_called_process_error():
47
+ with patch(
48
+ "hestia_keyring.subprocess.check_output",
49
+ side_effect=subprocess.CalledProcessError(1, "az"),
50
+ ):
51
+ assert HestiaArtifactsKeyring().get_credential(_FEED, "VssSessionToken") is None
52
+
53
+
54
+ def test_get_credential_returns_none_on_timeout():
55
+ with patch(
56
+ "hestia_keyring.subprocess.check_output",
57
+ side_effect=subprocess.TimeoutExpired("az", 30),
58
+ ):
59
+ assert HestiaArtifactsKeyring().get_credential(_FEED, "VssSessionToken") is None
60
+
61
+
62
+ def test_get_credential_returns_none_when_az_not_on_path():
63
+ with patch("hestia_keyring.subprocess.check_output", side_effect=FileNotFoundError):
64
+ assert HestiaArtifactsKeyring().get_credential(_FEED, "VssSessionToken") is None
65
+
66
+
67
+ def test_get_credential_returns_none_on_empty_token():
68
+ with patch("hestia_keyring.subprocess.check_output", return_value=b"\n"):
69
+ assert HestiaArtifactsKeyring().get_credential(_FEED, "VssSessionToken") is None
70
+
71
+
72
+ def test_get_password_returns_token_string():
73
+ with patch("hestia_keyring.subprocess.check_output", return_value=f"{_TOKEN}\n".encode()):
74
+ assert HestiaArtifactsKeyring().get_password(_FEED, "VssSessionToken") == _TOKEN
75
+
76
+
77
+ def test_get_password_returns_none_for_non_feed_url():
78
+ assert HestiaArtifactsKeyring().get_password(_OTHER, "VssSessionToken") is None
79
+
80
+
81
+ def test_set_password_raises():
82
+ with pytest.raises(NotImplementedError):
83
+ HestiaArtifactsKeyring().set_password("svc", "user", "pass")
84
+
85
+
86
+ def test_delete_password_raises():
87
+ with pytest.raises(NotImplementedError):
88
+ HestiaArtifactsKeyring().delete_password("svc", "user")