mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 07:48:57 +00:00
Merge pull request #370 from PR0M3TH3AN/codex/add-method-to-handle-key-value-entries
Add key-value entry support
This commit is contained in:
@@ -367,6 +367,36 @@ class EntryManager:
|
|||||||
self.backup_manager.create_backup()
|
self.backup_manager.create_backup()
|
||||||
return index
|
return index
|
||||||
|
|
||||||
|
def add_key_value(
|
||||||
|
self,
|
||||||
|
label: str,
|
||||||
|
value: str,
|
||||||
|
*,
|
||||||
|
notes: str = "",
|
||||||
|
custom_fields=None,
|
||||||
|
archived: bool = False,
|
||||||
|
) -> int:
|
||||||
|
"""Add a new generic key/value entry."""
|
||||||
|
|
||||||
|
index = self.get_next_index()
|
||||||
|
|
||||||
|
data = self.vault.load_index()
|
||||||
|
data.setdefault("entries", {})
|
||||||
|
data["entries"][str(index)] = {
|
||||||
|
"type": EntryType.KEY_VALUE.value,
|
||||||
|
"kind": EntryType.KEY_VALUE.value,
|
||||||
|
"label": label,
|
||||||
|
"value": value,
|
||||||
|
"notes": notes,
|
||||||
|
"archived": archived,
|
||||||
|
"custom_fields": custom_fields or [],
|
||||||
|
}
|
||||||
|
|
||||||
|
self._save_index(data)
|
||||||
|
self.update_checksum()
|
||||||
|
self.backup_manager.create_backup()
|
||||||
|
return index
|
||||||
|
|
||||||
def get_nostr_key_pair(self, index: int, parent_seed: str) -> tuple[str, str]:
|
def get_nostr_key_pair(self, index: int, parent_seed: str) -> tuple[str, str]:
|
||||||
"""Return the npub and nsec for the specified entry."""
|
"""Return the npub and nsec for the specified entry."""
|
||||||
|
|
||||||
@@ -502,7 +532,8 @@ class EntryManager:
|
|||||||
entry = data.get("entries", {}).get(str(index))
|
entry = data.get("entries", {}).get(str(index))
|
||||||
|
|
||||||
if entry:
|
if entry:
|
||||||
if entry.get("type", entry.get("kind")) == EntryType.PASSWORD.value:
|
etype = entry.get("type", entry.get("kind"))
|
||||||
|
if etype in (EntryType.PASSWORD.value, EntryType.KEY_VALUE.value):
|
||||||
entry.setdefault("custom_fields", [])
|
entry.setdefault("custom_fields", [])
|
||||||
logger.debug(f"Retrieved entry at index {index}: {entry}")
|
logger.debug(f"Retrieved entry at index {index}: {entry}")
|
||||||
return entry
|
return entry
|
||||||
@@ -531,6 +562,7 @@ class EntryManager:
|
|||||||
label: Optional[str] = None,
|
label: Optional[str] = None,
|
||||||
period: Optional[int] = None,
|
period: Optional[int] = None,
|
||||||
digits: Optional[int] = None,
|
digits: Optional[int] = None,
|
||||||
|
value: Optional[str] = None,
|
||||||
custom_fields: List[Dict[str, Any]] | None = None,
|
custom_fields: List[Dict[str, Any]] | None = None,
|
||||||
**legacy,
|
**legacy,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -545,6 +577,7 @@ class EntryManager:
|
|||||||
:param label: (Optional) The new label for the entry.
|
:param label: (Optional) The new label for the entry.
|
||||||
:param period: (Optional) The new TOTP period in seconds.
|
:param period: (Optional) The new TOTP period in seconds.
|
||||||
:param digits: (Optional) The new number of digits for TOTP codes.
|
:param digits: (Optional) The new number of digits for TOTP codes.
|
||||||
|
:param value: (Optional) New value for key/value entries.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = self.vault.load_index()
|
data = self.vault.load_index()
|
||||||
@@ -578,12 +611,19 @@ class EntryManager:
|
|||||||
if label is not None:
|
if label is not None:
|
||||||
entry["label"] = label
|
entry["label"] = label
|
||||||
logger.debug(f"Updated label to '{label}' for index {index}.")
|
logger.debug(f"Updated label to '{label}' for index {index}.")
|
||||||
if username is not None:
|
if entry_type == EntryType.PASSWORD.value:
|
||||||
entry["username"] = username
|
if username is not None:
|
||||||
logger.debug(f"Updated username to '{username}' for index {index}.")
|
entry["username"] = username
|
||||||
if url is not None:
|
logger.debug(
|
||||||
entry["url"] = url
|
f"Updated username to '{username}' for index {index}."
|
||||||
logger.debug(f"Updated URL to '{url}' for index {index}.")
|
)
|
||||||
|
if url is not None:
|
||||||
|
entry["url"] = url
|
||||||
|
logger.debug(f"Updated URL to '{url}' for index {index}.")
|
||||||
|
elif entry_type == EntryType.KEY_VALUE.value:
|
||||||
|
if value is not None:
|
||||||
|
entry["value"] = value
|
||||||
|
logger.debug(f"Updated value for index {index}.")
|
||||||
|
|
||||||
if archived is None and "blacklisted" in legacy:
|
if archived is None and "blacklisted" in legacy:
|
||||||
archived = legacy["blacklisted"]
|
archived = legacy["blacklisted"]
|
||||||
@@ -797,6 +837,29 @@ class EntryManager:
|
|||||||
entry.get("archived", entry.get("blacklisted", False)),
|
entry.get("archived", entry.get("blacklisted", False)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
elif etype == EntryType.KEY_VALUE.value:
|
||||||
|
value_field = str(entry.get("value", ""))
|
||||||
|
custom_fields = entry.get("custom_fields", [])
|
||||||
|
custom_match = any(
|
||||||
|
query_lower in str(cf.get("label", "")).lower()
|
||||||
|
or query_lower in str(cf.get("value", "")).lower()
|
||||||
|
for cf in custom_fields
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
label_match
|
||||||
|
or query_lower in value_field.lower()
|
||||||
|
or notes_match
|
||||||
|
or custom_match
|
||||||
|
):
|
||||||
|
results.append(
|
||||||
|
(
|
||||||
|
int(idx),
|
||||||
|
label,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
entry.get("archived", entry.get("blacklisted", False)),
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if label_match or notes_match:
|
if label_match or notes_match:
|
||||||
results.append(
|
results.append(
|
||||||
|
43
src/tests/test_key_value_entry.py
Normal file
43
src/tests/test_key_value_entry.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
|
||||||
|
|
||||||
|
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
|
from password_manager.entry_management import EntryManager
|
||||||
|
from password_manager.backup import BackupManager
|
||||||
|
from password_manager.config_manager import ConfigManager
|
||||||
|
|
||||||
|
|
||||||
|
def setup_entry_mgr(tmp_path: Path) -> EntryManager:
|
||||||
|
vault, _ = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD)
|
||||||
|
cfg_mgr = ConfigManager(vault, tmp_path)
|
||||||
|
backup_mgr = BackupManager(tmp_path, cfg_mgr)
|
||||||
|
return EntryManager(vault, backup_mgr)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_and_modify_key_value():
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
tmp_path = Path(tmpdir)
|
||||||
|
em = setup_entry_mgr(tmp_path)
|
||||||
|
|
||||||
|
idx = em.add_key_value("API", "abc123", notes="token")
|
||||||
|
entry = em.retrieve_entry(idx)
|
||||||
|
assert entry == {
|
||||||
|
"type": "key_value",
|
||||||
|
"kind": "key_value",
|
||||||
|
"label": "API",
|
||||||
|
"value": "abc123",
|
||||||
|
"notes": "token",
|
||||||
|
"archived": False,
|
||||||
|
"custom_fields": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
em.modify_entry(idx, value="def456")
|
||||||
|
updated = em.retrieve_entry(idx)
|
||||||
|
assert updated["value"] == "def456"
|
||||||
|
|
||||||
|
results = em.search_entries("def456")
|
||||||
|
assert results == [(idx, "API", None, None, False)]
|
Reference in New Issue
Block a user