diff --git a/libkeyringctl/cli.py b/libkeyringctl/cli.py index b5866c0..3334c17 100644 --- a/libkeyringctl/cli.py +++ b/libkeyringctl/cli.py @@ -15,6 +15,7 @@ from .keyring import convert from .keyring import export from .keyring import inspect_keyring from .keyring import list_keyring +from .types import TrustFilter from .util import absolute_path from .util import cwd from .verify import verify @@ -81,6 +82,12 @@ list_parser = subcommands.add_parser( help="list the certificates in the keyring", ) list_parser.add_argument("--main", action="store_true", help="List main signing keys instead of packager keys") +list_parser.add_argument( + "--trust", + choices=[e.value for e in TrustFilter], + default=TrustFilter.all.value, + help="Filter the list based on trust", +) list_parser.add_argument( "source", nargs="*", @@ -180,10 +187,12 @@ def main() -> None: # noqa: ignore=C901 target_dir=keyring_root.parent / "build", ) elif "list" == args.subcommand: + trust_filter = TrustFilter[args.trust] list_keyring( keyring_root=keyring_root, sources=args.source, main_keys=args.main, + trust_filter=trust_filter, ) elif "inspect" == args.subcommand: print( diff --git a/libkeyringctl/keyring.py b/libkeyringctl/keyring.py index 64aafb9..670b5af 100644 --- a/libkeyringctl/keyring.py +++ b/libkeyringctl/keyring.py @@ -25,9 +25,11 @@ from .sequoia import packet_signature_creation_time from .sequoia import packet_split from .trust import certificate_trust from .trust import certificate_trust_from_paths +from .trust import filter_by_trust from .trust import format_trust_label from .types import Fingerprint from .types import Trust +from .types import TrustFilter from .types import Uid from .types import Username from .util import contains_fingerprint @@ -1097,7 +1099,12 @@ def build( ) -def list_keyring(keyring_root: Path, sources: Optional[List[Path]] = None, main_keys: bool = False) -> None: +def list_keyring( + keyring_root: Path, + sources: Optional[List[Path]] = None, + main_keys: bool = False, + trust_filter: TrustFilter = TrustFilter.all, +) -> None: """List certificates in the keyring If sources contains directories, all certificate below them are considered. @@ -1108,6 +1115,7 @@ def list_keyring(keyring_root: Path, sources: Optional[List[Path]] = None, main_ sources: A list of username, fingerprint or directories from which to read PGP packet information (defaults to `keyring_root`) main_keys: List main keys instead of packager keys (defaults to False) + trust_filter: Filter the listing based on trust """ keyring_dir = keyring_root / ("main" if main_keys else "packager") @@ -1130,6 +1138,8 @@ def list_keyring(keyring_root: Path, sources: Optional[List[Path]] = None, main_ main_keys=get_fingerprints_from_paths([keyring_root / "main"]), all_fingerprints=get_fingerprints_from_paths([keyring_root]), ) + if not filter_by_trust(trust=trust, trust_filter=trust_filter): + continue trust_label = format_trust_label(trust=trust) print(f"{username:<{username_length}} {certificate.name} {trust_label}") diff --git a/libkeyringctl/trust.py b/libkeyringctl/trust.py index d7fa7ad..ec9796c 100644 --- a/libkeyringctl/trust.py +++ b/libkeyringctl/trust.py @@ -10,6 +10,7 @@ from typing import Set from .types import Color from .types import Fingerprint from .types import Trust +from .types import TrustFilter from .types import Uid from .util import contains_fingerprint from .util import get_cert_paths @@ -236,3 +237,26 @@ def format_trust_label(trust: Trust) -> str: Text label representing the trust status as literal and icon with colors """ return f"{trust_color(trust).value}{trust_icon(trust)} {trust.name}{Color.RST.value}" + + +def filter_by_trust(trust: Trust, trust_filter: TrustFilter) -> bool: + """Filters a trust by a given filter and returns true if within the rules + + Parameters + ---------- + trust: Trust to check for being filtered + trust_filter: Filter rules to check the trust against + + Returns + ------- + True if the given trust is within the filter rules + """ + trust_map = { + TrustFilter.unknown: [Trust.unknown], + TrustFilter.marginal: [Trust.marginal], + TrustFilter.full: [Trust.full], + TrustFilter.revoked: [Trust.revoked], + TrustFilter.unrevoked: [Trust.unknown, Trust.marginal, Trust.full], + TrustFilter.all: [Trust.revoked, Trust.unknown, Trust.marginal, Trust.full], + } + return trust in trust_map[trust_filter] diff --git a/libkeyringctl/types.py b/libkeyringctl/types.py index c87e2b3..98222cd 100644 --- a/libkeyringctl/types.py +++ b/libkeyringctl/types.py @@ -17,6 +17,15 @@ class Trust(Enum): full = auto() +class TrustFilter(Enum): + unknown = "unknown" + revoked = "revoked" + marginal = "marginal" + full = "full" + unrevoked = "unrevoked" + all = "all" + + TRUST_MAX_LENGTH: int = max([len(e.name) for e in Trust]) diff --git a/tests/test_keyring.py b/tests/test_keyring.py index d014b13..52bbfb3 100644 --- a/tests/test_keyring.py +++ b/tests/test_keyring.py @@ -728,6 +728,12 @@ def test_list_keyring(working_dir: Path, keyring_dir: Path) -> None: keyring.list_keyring(keyring_root=keyring_dir, sources=[path], main_keys=False) print_args = [mock_call[1][0] for mock_call in print_mock.mock_calls] assert name in print_args[0] and path.stem in print_args[0] + with patch("builtins.print") as print_mock: + keyring.list_keyring( + keyring_root=keyring_dir, sources=paths, main_keys=False, trust_filter=TrustFilter.revoked + ) + print_args = [mock_call[1][0] for mock_call in print_mock.mock_calls] + assert not print_args @create_certificate(username=Username("main"), uids=[Uid("main ")], keyring_type="main") diff --git a/tests/test_trust.py b/tests/test_trust.py index efe504d..344dfc2 100644 --- a/tests/test_trust.py +++ b/tests/test_trust.py @@ -8,12 +8,14 @@ from pytest import raises from libkeyringctl.trust import certificate_trust from libkeyringctl.trust import certificate_trust_from_paths +from libkeyringctl.trust import filter_by_trust from libkeyringctl.trust import format_trust_label from libkeyringctl.trust import trust_color from libkeyringctl.trust import trust_icon from libkeyringctl.types import Color from libkeyringctl.types import Fingerprint from libkeyringctl.types import Trust +from libkeyringctl.types import TrustFilter from libkeyringctl.types import Uid from libkeyringctl.types import Username @@ -327,3 +329,36 @@ def test_format_trust_label(trust_color_mock: Mock, trust_icon_mock: Mock, trust trust_icon_mock.return_value = "ICON" trust_color_mock.return_value = Color.GREEN assert f"{Color.GREEN.value}ICON {trust.name}{Color.RST.value}" == format_trust_label(trust) + + +@mark.parametrize( + "trust, trust_filter, result", + [ + (Trust.revoked, TrustFilter.unknown, False), + (Trust.full, TrustFilter.unknown, False), + (Trust.marginal, TrustFilter.unknown, False), + (Trust.unknown, TrustFilter.unknown, True), + (Trust.revoked, TrustFilter.marginal, False), + (Trust.full, TrustFilter.marginal, False), + (Trust.marginal, TrustFilter.marginal, True), + (Trust.unknown, TrustFilter.marginal, False), + (Trust.revoked, TrustFilter.full, False), + (Trust.full, TrustFilter.full, True), + (Trust.marginal, TrustFilter.full, False), + (Trust.unknown, TrustFilter.full, False), + (Trust.revoked, TrustFilter.revoked, True), + (Trust.full, TrustFilter.revoked, False), + (Trust.marginal, TrustFilter.revoked, False), + (Trust.unknown, TrustFilter.revoked, False), + (Trust.revoked, TrustFilter.unrevoked, False), + (Trust.full, TrustFilter.unrevoked, True), + (Trust.marginal, TrustFilter.unrevoked, True), + (Trust.unknown, TrustFilter.unrevoked, True), + (Trust.revoked, TrustFilter.all, True), + (Trust.full, TrustFilter.all, True), + (Trust.marginal, TrustFilter.all, True), + (Trust.unknown, TrustFilter.all, True), + ], +) +def test_filter_by_trust(trust: Trust, trust_filter: TrustFilter, result: bool) -> None: + assert filter_by_trust(trust=trust, trust_filter=trust_filter) == result