Coverage for app/venv/lib/python3.14/site-packages/weblate/vcs/gpg.py: 28%
60 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5from __future__ import annotations
7import subprocess
9from django.conf import settings
10from django.core.cache import cache
11from siphashc import siphash
13from weblate.trans.util import get_clean_env
14from weblate.utils.errors import report_error
15from weblate.utils.files import cleanup_error_message
17GPG_ERRORS: dict[str, str] = {}
20def gpg_error(name: str, error: Exception, silent: bool = False) -> None:
21 report_error(name)
23 if not silent:
24 GPG_ERRORS[name] = "{}\n{}\n{}".format(
25 cleanup_error_message(str(error)),
26 cleanup_error_message(getattr(error, "stderr", "")),
27 cleanup_error_message(getattr(error, "stdout", "")),
28 )
31def generate_gpg_key() -> str | None:
32 try:
33 subprocess.run(
34 [
35 "gpg",
36 "--batch",
37 "--pinentry-mode",
38 "loopback",
39 "--passphrase",
40 "",
41 "--quick-generate-key",
42 settings.WEBLATE_GPG_IDENTITY,
43 settings.WEBLATE_GPG_ALGO,
44 "default",
45 "never",
46 ],
47 env=get_clean_env(),
48 capture_output=True,
49 text=True,
50 check=True,
51 )
52 except (subprocess.CalledProcessError, OSError) as error:
53 gpg_error("GPG key generating", error)
54 return None
55 return get_gpg_key()
58def get_gpg_key(silent=False) -> str | None:
59 try:
60 result = subprocess.run(
61 [
62 "gpg",
63 "--batch",
64 "--with-colons",
65 "--list-secret-keys",
66 settings.WEBLATE_GPG_IDENTITY,
67 ],
68 capture_output=True,
69 env=get_clean_env(),
70 text=True,
71 check=True,
72 )
73 except (subprocess.CalledProcessError, OSError) as error:
74 gpg_error("GPG key listing", error, silent)
75 return None
76 for line in result.stdout.splitlines():
77 if not line.startswith("fpr:"): # codespell:ignore fpr
78 continue
79 return line.split(":")[9]
80 return None
83def gpg_cache_key(suffix: str) -> str:
84 return "gpg:{}:{}".format(
85 siphash("Weblate GPG hash", settings.WEBLATE_GPG_IDENTITY), suffix
86 )
89def get_gpg_sign_key() -> str | None:
90 """High level wrapper to cache key ID."""
91 if not settings.WEBLATE_GPG_IDENTITY: 91 ↛ 93line 91 didn't jump to line 93 because the condition on line 91 was always true
92 return None
93 cache_key = gpg_cache_key("id")
94 keyid = cache.get(cache_key)
95 if keyid is None:
96 keyid = get_gpg_key(silent=True)
97 if keyid is None:
98 keyid = generate_gpg_key()
99 if keyid:
100 cache.set(cache_key, keyid, 7 * 86400)
101 return keyid
104def get_gpg_public_key() -> str | None:
105 key = get_gpg_sign_key()
106 if key is None: 106 ↛ 108line 106 didn't jump to line 108 because the condition on line 106 was always true
107 return None
108 cache_key = gpg_cache_key("public")
109 data = cache.get(cache_key)
110 if not data:
111 try:
112 result = subprocess.run(
113 ["gpg", "--batch", "-armor", "--export", key],
114 env=get_clean_env(),
115 capture_output=True,
116 text=True,
117 check=True,
118 )
119 except (subprocess.CalledProcessError, OSError) as error:
120 gpg_error("GPG key public", error)
121 return None
122 data = result.stdout
123 cache.set(cache_key, data, 7 * 86400)
124 return data