Add support for groups of secrets

This commit is contained in:
2025-05-31 10:41:58 +02:00
parent f853ca81d0
commit 289352d872
3 changed files with 420 additions and 15 deletions

View File

@ -0,0 +1,242 @@
"""Unit tests for the password context class.
We are primarily testing whether the actions of the PasswordContext matches
those in the low level API.
"""
import random
import string
from pathlib import Path
from typing import cast
import pytest
import pykeepass
from sshecret_admin.services.keepass import PasswordContext
def random_string(length: int = 5) -> str:
"""Generate random string."""
chars = string.ascii_lowercase
return "".join(random.choice(chars) for _ in range(length))
def create_random_entries(
password_database: pykeepass.PyKeePass,
amount: int = 10,
prefix: str = "secret",
group: pykeepass.group.Group | None = None,
) -> None:
"""Create some random entries."""
if not group:
group = cast(pykeepass.group.Group, password_database.root_group)
for n in range(amount):
name = f"{prefix}-{n}"
username = "NONE"
password = random_string(12)
password_database.add_entry(
destination_group=group,
title=name,
username=username,
password=password,
)
password_database.save()
@pytest.fixture(name="password_database")
def password_db_fixture(tmp_path: Path):
"""Create a password database."""
filename = tmp_path / "kpdb.kdbx"
yield pykeepass.create_database(str(filename), password="test")
def test_add_entry(password_database: pykeepass.PyKeePass) -> None:
"""Test add entry."""
context = PasswordContext(password_database)
context.add_entry("testentry", "testsecret")
entry = password_database.find_entries(title="testentry", first=True)
assert entry is not None
assert isinstance(entry, pykeepass.entry.Entry)
assert entry.password == "testsecret"
def test_get_secret(password_database: pykeepass.PyKeePass) -> None:
"""Test get secret."""
context = PasswordContext(password_database)
context.add_entry("testentry", "testsecret")
secret = context.get_secret("testentry")
assert secret is not None
assert secret == "testsecret"
def test_get_available_secrets(password_database: pykeepass.PyKeePass) -> None:
"""Test get_available_secrets."""
create_random_entries(password_database, 10)
context = PasswordContext(password_database)
available_secrets = context.get_available_secrets()
assert len(available_secrets) == 10
for n in range(10):
assert f"secret-{n}" in available_secrets
def test_delete_entry(password_database: pykeepass.PyKeePass) -> None:
"""Test deletion of entry."""
create_random_entries(password_database, 3)
context = PasswordContext(password_database)
available_secrets = context.get_available_secrets()
assert len(available_secrets) == 3
context.delete_entry("secret-2")
entry = password_database.find_entries(title="secret-2", first=True)
assert entry is None
available_secrets = context.get_available_secrets()
assert len(available_secrets) == 2
def test_get_secret_groups(password_database: pykeepass.PyKeePass) -> None:
"""Test get secret groups."""
# We create a hierarchy of groups to test how that parses.
root_group = password_database.root_group
first_group = password_database.add_group(
root_group, "level_one", notes="A group in the root"
)
password_database.add_group(
first_group, "level_two", notes="A group one level down"
)
# Another group at the root, without a note
password_database.add_group(root_group, "free_group")
password_database.save()
context = PasswordContext(password_database)
groups = context.get_secret_groups()
assert len(groups) == 3
def test_get_secret_groups_regex(password_database: pykeepass.PyKeePass) -> None:
"""Get secret groups matching a regex."""
# create some groups matching a pattern
for n in range(4):
password_database.add_group(password_database.root_group, f"foo-{n}")
for n in range(3):
parent_group = password_database.find_groups(name="foo-1", first=True)
password_database.add_group(parent_group, f"bar-{n}")
password_database.save()
context = PasswordContext(password_database)
foo_groups = context.get_secret_groups("foo-.*")
assert len(foo_groups) == 4
bar_groups = context.get_secret_groups("bar-.*")
assert len(bar_groups) == 3
def test_add_group(password_database: pykeepass.PyKeePass) -> None:
"""Test add_group."""
context = PasswordContext(password_database)
context.add_group("test_group", "Test Group")
assert password_database.find_groups(name="test_group", first=True) is not None
# add a nested group below the first one
context.add_group("nested_group", "Nested test group", "test_group")
group = password_database.find_groups(name="nested_group", first=True)
assert group is not None
assert isinstance(group, pykeepass.group.Group)
parent_group = group.parentgroup
assert parent_group.name == "test_group"
def test_set_group_description(password_database: pykeepass.PyKeePass) -> None:
"""Test setting the group description."""
context = PasswordContext(password_database)
context.add_group("test_group", "Test Group")
kp_group = password_database.find_groups(name="test_group", first=True)
assert isinstance(kp_group, pykeepass.group.Group)
assert kp_group.notes == "Test Group"
context.set_group_description("test_group", "New Description")
kp_group = password_database.find_groups(name="test_group", first=True)
assert isinstance(kp_group, pykeepass.group.Group)
assert kp_group.notes == "New Description"
def test_add_entry_with_group(password_database: pykeepass.PyKeePass) -> None:
"""Test adding an entry with a group."""
context = PasswordContext(password_database)
context.add_group("test_group", "A test group")
context.add_entry("test_entry", "test_secret", group_name="test_group")
entry = password_database.find_entries(title="test_entry", first=True)
assert entry is not None
assert isinstance(entry, pykeepass.entry.Entry)
assert entry.group.name == "test_group"
def test_move_entry(password_database: pykeepass.PyKeePass) -> None:
"""Test moving entries between groups."""
context = PasswordContext(password_database)
context.add_group("test_group", "A test group")
context.add_group("test_group_2", "Another test group")
context.add_entry("test_entry", "test_secret")
entry = password_database.find_entries(title="test_entry", first=True)
assert isinstance(entry, pykeepass.entry.Entry)
assert entry.group.is_root_group is True
context.set_secret_group("test_entry", "test_group")
entry = password_database.find_entries(title="test_entry", first=True)
assert isinstance(entry, pykeepass.entry.Entry)
assert entry.group.is_root_group is False
assert entry.group.name == "test_group"
context.set_secret_group("test_entry", "test_group_2")
entry = password_database.find_entries(title="test_entry", first=True)
assert isinstance(entry, pykeepass.entry.Entry)
assert entry.group.is_root_group is False
assert entry.group.name == "test_group_2"
context.set_secret_group("test_entry", None)
entry = password_database.find_entries(title="test_entry", first=True)
assert isinstance(entry, pykeepass.entry.Entry)
assert entry.group.is_root_group is True
def test_move_group(password_database: pykeepass.PyKeePass) -> None:
"""Test moving a group."""
context = PasswordContext(password_database)
context.add_group("test_group", "A test group")
context.add_group("parent_group", "A parent group")
context.move_group("test_group", "parent_group")
kp_group = password_database.find_groups(name="test_group", first=True)
assert isinstance(kp_group, pykeepass.group.Group)
assert kp_group.parentgroup.name == "parent_group"
def test_delete_group(password_database: pykeepass.PyKeePass) -> None:
"""Test group deletion."""
context = PasswordContext(password_database)
context.add_group("test_group", "A test group")
# Add some entries to this group.
kp_group = password_database.find_groups(name="test_group", first=True)
assert isinstance(kp_group, pykeepass.group.Group)
create_random_entries(password_database, amount=10, group=kp_group)
context.delete_group("test_group")
kp_group = password_database.find_groups(name="test_group", first=True)
assert kp_group is None
# Check if the secrets are still there.
secrets = context.get_available_secrets()
assert len(secrets) == 10