Skip to content

Commit aafdb50

Browse files
committed
fix(config): avoid loading key from system-wide settings
This was discouraged for years and is a bad security practice that might cause token leaking to a different server.
1 parent 89ba166 commit aafdb50

5 files changed

Lines changed: 35 additions & 34 deletions

File tree

wlc/config.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,30 @@
88

99
import os.path
1010
from configparser import NoOptionError, RawConfigParser
11-
from typing import TYPE_CHECKING
11+
from typing import cast
1212

13-
from xdg.BaseDirectory import load_config_paths # type: ignore[import-untyped]
13+
from xdg.BaseDirectory import load_first_config # type: ignore[import-untyped]
1414

1515
import wlc
1616

17-
if TYPE_CHECKING:
18-
from collections.abc import Generator
19-
2017
__all__ = ["NoOptionError", "WeblateConfig"]
2118

2219

2320
class WeblateConfig(RawConfigParser):
2421
"""Configuration parser wrapper with defaults."""
2522

26-
def __init__(self, section="weblate") -> None:
23+
def __init__(self, section: str = "weblate") -> None:
2724
"""Construct WeblateConfig object."""
2825
super().__init__(delimiters=("=",))
29-
self.section = section
26+
self.section: str = section
27+
self.cli_key: str | None = None
28+
self.cli_url: str | None = None
3029
self.set_defaults()
3130

3231
def set_defaults(self) -> None:
3332
"""Set default values."""
3433
self.add_section("keys")
3534
self.add_section(self.section)
36-
self.set(self.section, "key", "")
3735
self.set(self.section, "url", wlc.API_URL)
3836
self.set(self.section, "retries", "0")
3937
self.set(self.section, "timeout", "300")
@@ -44,23 +42,27 @@ def set_defaults(self) -> None:
4442
self.set(self.section, "backoff_factor", "0")
4543

4644
@staticmethod
47-
def find_configs() -> Generator[str]:
45+
def find_config() -> str | None:
4846
# Handle Windows specifically
4947
for envname in ("APPDATA", "LOCALAPPDATA"):
5048
if path := os.environ.get(envname):
5149
win_path = os.path.join(path, "weblate.ini")
5250
if os.path.exists(win_path):
53-
yield win_path
51+
return win_path
5452

5553
# Generic XDG paths
56-
yield from load_config_paths("weblate")
57-
yield from load_config_paths("weblate.ini")
54+
for filename in ("weblate", "weblate.ini"):
55+
if config := load_first_config(filename):
56+
return config
57+
58+
return None
5859

59-
def load(self, path=None) -> None:
60+
def load(self, path: str | None = None) -> None:
6061
"""Load configuration from XDG paths."""
6162
if path is None:
62-
path = list(self.find_configs())
63-
self.read(path)
63+
path = self.find_config()
64+
if path:
65+
self.read(path)
6466

6567
# Try reading from current dir
6668
cwd = os.path.abspath(".")
@@ -74,15 +76,10 @@ def load(self, path=None) -> None:
7476
prev = cwd
7577
cwd = os.path.dirname(cwd)
7678

77-
def get_url_key(self):
79+
def get_url_key(self) -> tuple[str, str]:
7880
"""Get API URL and key."""
79-
url = self.get(self.section, "url")
80-
key = self.get(self.section, "key")
81-
if not key:
82-
try:
83-
key = self.get("keys", url)
84-
except NoOptionError:
85-
key = ""
81+
url = self.cli_url or cast("str", self.get(self.section, "url"))
82+
key = self.cli_key or cast("str", self.get("keys", url, fallback=""))
8683
return url, key
8784

8885
def get_request_options(self):

wlc/main.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -794,10 +794,10 @@ def parse_settings(args, settings):
794794
for section, key, value in settings:
795795
config.set(section, key, value)
796796

797-
for override in ("key", "url"):
798-
value = getattr(args, override)
799-
if value is not None:
800-
config.set(args.config_section, override, value)
797+
if args.key:
798+
config.cli_key = args.key
799+
if args.url:
800+
config.cli_url = args.url
801801

802802
return config
803803

wlc/test_base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
class ResponseHandler:
2222
"""responses response handler."""
2323

24-
def __init__(self, body, filename, auth=False) -> None:
24+
def __init__(self, body: bytes, filename: str, auth: bool = False) -> None:
2525
"""Construct response handler object."""
2626
self.body = body
2727
self.filename = filename
@@ -111,7 +111,9 @@ def format_multipart_body(body, content_type):
111111
return digest.hexdigest()
112112

113113

114-
def register_uri(path, domain="http://127.0.0.1:8000/api", auth=False) -> None:
114+
def register_uri(
115+
path: str, domain: str = "http://127.0.0.1:8000/api", auth: bool = False
116+
) -> None:
115117
"""Simplified URL registration."""
116118
filename = os.path.join(DATA_TEST_BASE, path.replace("/", "-"))
117119
url = f"{domain}/{path}/"

wlc/test_data/weblate.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
[weblate]
22
url = http://127.0.0.1:8000/api/
3-
key = KEY
3+
4+
[keys]
5+
http://127.0.0.1:8000/api/ = KEY

wlc/test_main.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,16 @@ def test_config_section(self) -> None:
9191
self.assertIn("Hello", output)
9292

9393
def test_config_key(self) -> None:
94-
"""Configuration using custom config file section and key set."""
94+
"""Configuration using custom config file section and key set is ignored."""
9595
output = self.execute(
9696
["--config", TEST_CONFIG, "--config-section", "withkey", "show", "acl"],
9797
settings=False,
98+
expected=1,
9899
)
99-
self.assertIn("ACL", output)
100+
self.assertIn("Error: You don't have permission to access this object", output)
100101

101102
def test_config_appdata(self) -> None:
102-
"""Configuration using custom config file section and key set."""
103+
"""Verify keys are loaded from the [keys] section in APPDATA-based config."""
103104
output = self.execute(["show", "acl"], settings=False, expected=1)
104105
self.assertIn("You don't have permission to access this object", output)
105106
try:
@@ -122,7 +123,6 @@ def test_config_cwd(self) -> None:
122123
def test_default_config_values(self) -> None:
123124
"""Test default parser values."""
124125
config = WeblateConfig()
125-
self.assertEqual(config.get("weblate", "key"), "")
126126
self.assertEqual(config.get("weblate", "retries"), "0")
127127
self.assertEqual(config.get("weblate", "timeout"), "300")
128128
self.assertEqual(

0 commit comments

Comments
 (0)