Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

209 lines
7.7 KiB
Python

from __future__ import annotations
import subprocess
import keyring
import config.llm_credentials as llm_credentials
import config.llm_keyring as llm_keyring
from config.llm_auth.credentials import (
has_llm_api_key,
llm_api_key_source,
resolve_for_request,
status,
)
from config.llm_auth.records import save_provider_auth_record
from tests.shared.keyring_backend import MemoryKeyring
class _MacOSKeyringBackend:
pass
_MacOSKeyringBackend.__module__ = "keyring.backends.macOS"
def _security_tool_path(name: str) -> str:
return f"/usr/bin/{name}"
def _darwin_platform() -> str:
return "Darwin"
def test_resolve_env_credential_prefers_env_over_keyring(monkeypatch) -> None:
monkeypatch.setenv("GITLAB_ACCESS_TOKEN", "from-env")
monkeypatch.delenv("OPENSRE_DISABLE_KEYRING", raising=False)
previous_backend = keyring.get_keyring()
keyring.set_keyring(MemoryKeyring())
try:
llm_credentials.save_llm_api_key("GITLAB_ACCESS_TOKEN", "from-keyring")
assert llm_credentials.resolve_env_credential("GITLAB_ACCESS_TOKEN") == "from-env"
finally:
keyring.set_keyring(previous_backend)
def test_unmanaged_llm_api_key_source_reports_env_keyring_and_none(monkeypatch) -> None:
monkeypatch.delenv("OPENSRE_DISABLE_KEYRING", raising=False)
monkeypatch.delenv("EXPERIMENTAL_API_KEY", raising=False)
previous_backend = keyring.get_keyring()
keyring.set_keyring(MemoryKeyring())
try:
assert llm_api_key_source("EXPERIMENTAL_API_KEY") == "none"
llm_credentials.save_llm_api_key("EXPERIMENTAL_API_KEY", "from-keyring")
assert llm_api_key_source("EXPERIMENTAL_API_KEY") == "keyring"
monkeypatch.setenv("EXPERIMENTAL_API_KEY", "from-env")
assert llm_api_key_source("EXPERIMENTAL_API_KEY") == "env"
finally:
keyring.set_keyring(previous_backend)
def test_managed_llm_api_key_source_uses_metadata_without_reading_secret(
monkeypatch, tmp_path
) -> None:
monkeypatch.delenv("OPENSRE_DISABLE_KEYRING", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.setenv("OPENSRE_LLM_AUTH_METADATA_PATH", str(tmp_path / "llm-auth.json"))
monkeypatch.setattr(llm_keyring.platform, "system", _darwin_platform)
monkeypatch.setattr(llm_keyring.shutil, "which", _security_tool_path)
monkeypatch.setattr(llm_keyring.keyring, "get_keyring", _MacOSKeyringBackend)
def _run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
assert command == [
"/usr/bin/security",
"find-generic-password",
"-s",
"opensre.llm",
"-a",
"OPENAI_API_KEY",
]
assert kwargs["check"] is False
return subprocess.CompletedProcess(command, 0)
def _get_password(_service: str, _username: str) -> str:
raise AssertionError("metadata source check must not read the keychain secret")
monkeypatch.setattr(llm_keyring.subprocess, "run", _run)
monkeypatch.setattr(llm_keyring.keyring, "get_password", _get_password)
save_provider_auth_record(
provider="openai",
auth_name="openai",
kind="api_key",
source="keyring",
detail="OPENAI_API_KEY stored in the system keychain.",
env_var="OPENAI_API_KEY",
)
assert llm_api_key_source("OPENAI_API_KEY") == "metadata"
assert has_llm_api_key("OPENAI_API_KEY") is True
def test_managed_missing_metadata_reports_none_without_reading_secret(
monkeypatch, tmp_path
) -> None:
monkeypatch.delenv("OPENSRE_DISABLE_KEYRING", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.setenv("OPENSRE_LLM_AUTH_METADATA_PATH", str(tmp_path / "llm-auth.json"))
monkeypatch.setattr(llm_keyring.platform, "system", _darwin_platform)
monkeypatch.setattr(llm_keyring.shutil, "which", _security_tool_path)
monkeypatch.setattr(llm_keyring.keyring, "get_keyring", _MacOSKeyringBackend)
def _run(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
return subprocess.CompletedProcess(command, 44)
def _get_password(_service: str, _username: str) -> str:
raise AssertionError("metadata source check must not read the keychain secret")
monkeypatch.setattr(llm_keyring.subprocess, "run", _run)
monkeypatch.setattr(llm_keyring.keyring, "get_password", _get_password)
assert llm_api_key_source("OPENAI_API_KEY") == "none"
assert has_llm_api_key("OPENAI_API_KEY") is False
def test_request_resolution_marks_deleted_keychain_metadata_stale(monkeypatch, tmp_path) -> None:
monkeypatch.delenv("OPENSRE_DISABLE_KEYRING", raising=False)
monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
monkeypatch.setenv("OPENSRE_LLM_AUTH_METADATA_PATH", str(tmp_path / "llm-auth.json"))
save_provider_auth_record(
provider="deepseek",
auth_name="deepseek",
kind="api_key",
source="keyring",
detail="DEEPSEEK_API_KEY stored in the system keychain.",
env_var="DEEPSEEK_API_KEY",
)
previous_backend = keyring.get_keyring()
keyring.set_keyring(MemoryKeyring())
try:
before = status("deepseek")
resolution = resolve_for_request("deepseek")
after = status("deepseek")
finally:
keyring.set_keyring(previous_backend)
assert before.configured is True
assert before.stale is False
assert resolution.ok is False
assert after.configured is True
assert after.stale is True
assert after.verified is False
assert "Missing credential" in after.detail
def test_llm_credential_record_round_trips_in_keyring(monkeypatch) -> None:
monkeypatch.delenv("OPENSRE_DISABLE_KEYRING", raising=False)
previous_backend = keyring.get_keyring()
keyring.set_keyring(MemoryKeyring())
try:
llm_credentials.save_llm_credential_record(
"provider-auth:deepseek",
{"provider": "deepseek", "source": "keyring", "empty": ""},
)
assert llm_credentials.resolve_llm_credential_record("provider-auth:deepseek") == {
"provider": "deepseek",
"source": "keyring",
}
llm_credentials.delete_llm_credential_record("provider-auth:deepseek")
assert llm_credentials.resolve_llm_credential_record("provider-auth:deepseek") == {}
finally:
keyring.set_keyring(previous_backend)
def test_get_keyring_setup_instructions_for_linux_without_gnome_keyring(monkeypatch) -> None:
backend_class = type("Keyring", (), {})
backend_class.__module__ = "keyring.backends.fail"
monkeypatch.delenv("OPENSRE_DISABLE_KEYRING", raising=False)
monkeypatch.delenv("DBUS_SESSION_BUS_ADDRESS", raising=False)
monkeypatch.setattr(llm_keyring.platform, "system", lambda: "Linux")
monkeypatch.setattr(llm_keyring.shutil, "which", lambda _name: None)
monkeypatch.setattr(llm_keyring.keyring, "get_keyring", lambda: backend_class())
lines = llm_credentials.get_keyring_setup_instructions("ANTHROPIC_API_KEY")
assert lines[0] == "Current keyring backend: keyring.backends.fail.Keyring."
assert "missing the GNOME Keyring daemon" in lines[1]
assert any(
"sudo apt update && sudo apt install -y gnome-keyring dbus-user-session" in line
for line in lines
)
assert any("dbus-run-session -- sh" in line for line in lines)
def test_get_keyring_setup_instructions_when_keyring_is_disabled(monkeypatch) -> None:
monkeypatch.setenv("OPENSRE_DISABLE_KEYRING", "1")
lines = llm_credentials.get_keyring_setup_instructions("OPENAI_API_KEY")
assert lines == (
"Secure local credential storage is disabled by OPENSRE_DISABLE_KEYRING.",
"Unset OPENSRE_DISABLE_KEYRING and rerun `opensre onboard` to save OPENAI_API_KEY securely.",
)