/
/
/
1"""
2Gate SECURE_STRING config writes behind the orthogonal secret tag.
3
4The provider performs NO encryption: plaintext is handed to MA's
5``save_*_config``, which encrypts via ``Config.to_raw()`` before
6persisting. This module only decides whether a write touching a
7SECURE_STRING entry is *allowed* given the caller's enabled tags.
8"""
9
10from __future__ import annotations
11
12from typing import TYPE_CHECKING
13
14from fastmcp.exceptions import ToolError
15from music_assistant_models.enums import ConfigEntryType
16
17if TYPE_CHECKING:
18 from collections.abc import Mapping
19
20 from music_assistant_models.config_entries import ConfigEntry
21
22
23def is_secret_key(entries: Mapping[str, ConfigEntry], key: str) -> bool:
24 """
25 Return True iff ``key`` maps to a SECURE_STRING ConfigEntry.
26
27 :param entries: ConfigEntry definitions keyed by config key.
28 :param key: The config key to test.
29 """
30 entry = entries.get(key)
31 return entry is not None and entry.type == ConfigEntryType.SECURE_STRING
32
33
34def gate_secret_writes(
35 entries: Mapping[str, ConfigEntry],
36 values: Mapping[str, object],
37 *,
38 secret_tag_enabled: bool,
39) -> None:
40 """
41 Raise ``ToolError`` if ``values`` writes a SECURE_STRING key without the tag.
42
43 The check is atomic â the whole payload is rejected (the provider does
44 not split it), naming the first offending secret key.
45
46 :param entries: ConfigEntry definitions for the target.
47 :param values: The proposed keyâvalue writes.
48 :param secret_tag_enabled: Whether ``config:write:secret`` is enabled.
49 """
50 if secret_tag_enabled:
51 return
52 for key in values:
53 if is_secret_key(entries, key):
54 raise ToolError(f"SECURE_STRING write requires config:write:secret tag (key={key!r})")
55