diff --git a/src/ahriman/core/alpm/pacman_database.py b/src/ahriman/core/alpm/pacman_database.py index 75355461..3886a8f5 100644 --- a/src/ahriman/core/alpm/pacman_database.py +++ b/src/ahriman/core/alpm/pacman_database.py @@ -138,8 +138,14 @@ class PacmanDatabase(SyncHttpClient): Args: force(bool): force database synchronization (same as ``pacman -Syy``) + + Raises: + PacmanError: on operation error (invalid scheme or incomplete configuration) """ - server = next(iter(self.database.servers)) + try: + server = next(iter(self.database.servers)) + except StopIteration: + raise PacmanError("No configured servers available for database") from None filename = f"{self.database.name}.files.tar.gz" url = f"{server}/{filename}" diff --git a/src/ahriman/core/exceptions.py b/src/ahriman/core/exceptions.py index 9972c8ad..6908e8fd 100644 --- a/src/ahriman/core/exceptions.py +++ b/src/ahriman/core/exceptions.py @@ -116,6 +116,19 @@ class GitRemoteError(RuntimeError): RuntimeError.__init__(self, "Git remote failed") +class GPGError(RuntimeError): + """ + PGP/GPG related exception + """ + + def __init__(self, details: str) -> None: + """ + Args: + details(str): details of the exception + """ + RuntimeError.__init__(self, f"GPG operation failed: {details}") + + class InitializeError(RuntimeError): """ base service initialization exception diff --git a/src/ahriman/core/sign/gpg.py b/src/ahriman/core/sign/gpg.py index 865ab0de..70c8f413 100644 --- a/src/ahriman/core/sign/gpg.py +++ b/src/ahriman/core/sign/gpg.py @@ -20,7 +20,7 @@ from pathlib import Path from ahriman.core.configuration import Configuration -from ahriman.core.exceptions import BuildError +from ahriman.core.exceptions import BuildError, GPGError from ahriman.core.http import SyncHttpClient from ahriman.core.utils import check_output from ahriman.models.sign_settings import SignSettings @@ -147,12 +147,19 @@ class GPG(SyncHttpClient): Returns: str: full PGP key fingerprint + + Raises: + GPGError: if key is in wrong format """ - metadata = check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger) # fingerprint line will be like # fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2: - fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines())) - return fingerprint.split(":")[-2] + metadata = check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger) + + try: + fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines())) + return fingerprint.split(":")[-2] + except (IndexError, StopIteration): + raise GPGError(f"key {key} has invalid metadata") from None def key_import(self, server: str, key: str) -> None: """ diff --git a/src/ahriman/models/user.py b/src/ahriman/models/user.py index cec95385..ddfd19fd 100644 --- a/src/ahriman/models/user.py +++ b/src/ahriman/models/user.py @@ -88,8 +88,12 @@ class User: """ if not self.password: return None - algo = next(segment for segment in self.password.split("$") if segment) - return f"${algo}$" + + try: + algo = next(segment for segment in self.password.split("$") if segment) + return f"${algo}$" + except StopIteration: + return None @staticmethod def generate_password(length: int) -> str: diff --git a/tests/ahriman/core/alpm/test_pacman_database.py b/tests/ahriman/core/alpm/test_pacman_database.py index 4dcc0d53..3ed9dc3e 100644 --- a/tests/ahriman/core/alpm/test_pacman_database.py +++ b/tests/ahriman/core/alpm/test_pacman_database.py @@ -183,6 +183,15 @@ def test_sync_files_local(pacman_database: PacmanDatabase, mocker: MockerFixture copy_mock.assert_called_once_with(Path("/var/core.files.tar.gz"), pytest.helpers.anyvar(int)) +def test_sync_files_no_servers(pacman_database: PacmanDatabase) -> None: + """ + must raise PacmanError if no servers are configured + """ + pacman_database.database.servers = [] + with pytest.raises(PacmanError): + pacman_database.sync_files(force=False) + + def test_sync_files_unknown_source(pacman_database: PacmanDatabase) -> None: """ must raise an exception in case if server scheme is unsupported diff --git a/tests/ahriman/core/sign/test_gpg.py b/tests/ahriman/core/sign/test_gpg.py index 5df4e8a0..b67c5372 100644 --- a/tests/ahriman/core/sign/test_gpg.py +++ b/tests/ahriman/core/sign/test_gpg.py @@ -5,6 +5,7 @@ from pathlib import Path from pytest_mock import MockerFixture from ahriman.core.configuration import Configuration +from ahriman.core.exceptions import GPGError from ahriman.core.sign.gpg import GPG from ahriman.models.sign_settings import SignSettings @@ -113,6 +114,15 @@ fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:""") check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key, logger=gpg.logger) +def test_key_fingerprint_invalid(gpg: GPG, mocker: MockerFixture) -> None: + """ + must raise GPGError if no fingerprint found in output + """ + mocker.patch("ahriman.core.sign.gpg.check_output", return_value="no fingerprint here") + with pytest.raises(GPGError): + gpg.key_fingerprint("0xCE3C45C2") + + def test_key_import(gpg: GPG, mocker: MockerFixture) -> None: """ must import PGP key from the server diff --git a/tests/ahriman/models/test_user.py b/tests/ahriman/models/test_user.py index a04fdeef..0cb0c072 100644 --- a/tests/ahriman/models/test_user.py +++ b/tests/ahriman/models/test_user.py @@ -12,6 +12,8 @@ def test_algo() -> None: """ assert User(username="user", password=None, access=UserAccess.Read).algo is None assert User(username="user", password="", access=UserAccess.Read).algo is None + assert User(username="user", password="$$$", access=UserAccess.Read).algo is None + assert User( username="user", password="$6$rounds=656000$mWBiecMPrHAL1VgX$oU4Y5HH8HzlvMaxwkNEJjK13ozElyU1wAHBoO/WW5dAaE4YEfnB0X3FxbynKMl4FBdC3Ovap0jINz4LPkNADg0",