Commit 36f03215 authored by Maciej Gol's avatar Maciej Gol

Black

parent eb59c788
......@@ -6,4 +6,8 @@ from op_askpass.configuration import get_configuration_directory
def main() -> None:
item_name = os.environ["OP_ASKPASS_ITEM_NAME"]
print(operations.get_password_from_op(executable=get_configuration_directory() / "op", item_name=item_name))
print(
operations.get_password_from_op(
executable=get_configuration_directory() / "op", item_name=item_name
)
)
......@@ -27,9 +27,17 @@ def list_keys() -> None:
@main.command()
@click.argument("op_domain")
@click.argument("op_email")
@click.option("--install-path", type=Path, help="Where the 1Password client should be installed.")
@click.option("--verify/--no-verify", default=True, help="Should the download 1Password client binary be verified.")
def setup_op_client(op_domain: str, op_email: str, verify: bool, install_path: Optional[Path]) -> None:
@click.option(
"--install-path", type=Path, help="Where the 1Password client should be installed."
)
@click.option(
"--verify/--no-verify",
default=True,
help="Should the download 1Password client binary be verified.",
)
def setup_op_client(
op_domain: str, op_email: str, verify: bool, install_path: Optional[Path]
) -> None:
""" Download the 1Password API client and login to email in domain. """
operations.setup_op_client(
download_url="https://cache.agilebits.com/dist/1P/op/pkg/v0.5.7/op_linux_amd64_v0.5.7.zip",
......@@ -42,10 +50,17 @@ def setup_op_client(op_domain: str, op_email: str, verify: bool, install_path: O
@main.command()
@click.argument("op_domain")
@click.option("--install-path", type=Path, help="Where the 1Password client was installed.")
@click.option(
"--install-path", type=Path, help="Where the 1Password client was installed."
)
def login(op_domain: str, install_path: Optional[Path]) -> None:
install_path = install_path or get_configuration_directory()
operations.login_to_op(install_path / "op", op_domain, key_store=get_default_key_store(), key_loader=SSHKeyLoader())
operations.login_to_op(
install_path / "op",
op_domain,
key_store=get_default_key_store(),
key_loader=SSHKeyLoader(),
)
@main.command()
......
......@@ -17,7 +17,11 @@ class KeyFingerprint:
if not isinstance(other, KeyFingerprint):
return NotImplemented
return self.length == other.length and self.hash == other.hash and self.key_type == other.key_type
return (
self.length == other.length
and self.hash == other.hash
and self.key_type == other.key_type
)
def __hash__(self) -> int:
return hash(self.length) + hash(self.hash) + hash(self.key_type)
......
......@@ -4,8 +4,12 @@ from pathlib import Path
import pytest
from op_askpass.fingerprint_generator import AbstractFingerprintGenerator, MD5FingerprintGenerator, \
SSHKeyGenFingerprintGenerator, KeyFingerprint
from op_askpass.fingerprint_generator import (
AbstractFingerprintGenerator,
MD5FingerprintGenerator,
SSHKeyGenFingerprintGenerator,
KeyFingerprint,
)
HERE = Path(__file__).parent
......@@ -17,7 +21,9 @@ class AbstractFingerprintGeneratorTests(abc.ABC):
file_path.write_bytes(b"xxx")
return file_path
def test_for_path_should_correctly_generate_fingerprint(self, test_key_file_path: Path) -> None:
def test_for_path_should_correctly_generate_fingerprint(
self, test_key_file_path: Path
) -> None:
generator = self.fingerprint_generator_factory()
assert isinstance(generator.for_path(test_key_file_path), KeyFingerprint)
......@@ -36,7 +42,12 @@ class TestMD5FingerprintGenerator(AbstractFingerprintGeneratorTests):
file_path = tmp_path / "yada.txt"
file_path.write_bytes(b"xxx")
assert generator.for_path(file_path) == KeyFingerprint(length=32, hash='f561aaf6ef0bf14d4208bb46a4ccb3ad', comment="some-key", key_type="md5")
assert generator.for_path(file_path) == KeyFingerprint(
length=32,
hash="f561aaf6ef0bf14d4208bb46a4ccb3ad",
comment="some-key",
key_type="md5",
)
class TestSSHKeyGenFingerprintGenerator(AbstractFingerprintGeneratorTests):
......@@ -47,7 +58,11 @@ class TestSSHKeyGenFingerprintGenerator(AbstractFingerprintGeneratorTests):
def fingerprint_generator_factory(self) -> AbstractFingerprintGenerator:
return SSHKeyGenFingerprintGenerator()
def test_for_path_should_generate_correct_checksum(self, test_key_file_path: Path) -> None:
def test_for_path_should_generate_correct_checksum(
self, test_key_file_path: Path
) -> None:
generator = self.fingerprint_generator_factory()
assert generator.for_path(test_key_file_path) == KeyFingerprint.from_str('2048 SHA256:IKJs9nCZuYQ2tND/G2kCM1/+ggJsWMnWn+iP7y4FXDc test@key (RSA)')
assert generator.for_path(test_key_file_path) == KeyFingerprint.from_str(
"2048 SHA256:IKJs9nCZuYQ2tND/G2kCM1/+ggJsWMnWn+iP7y4FXDc test@key (RSA)"
)
......@@ -4,19 +4,28 @@ import subprocess
from pathlib import Path
from typing import List, Set
from op_askpass.fingerprint_generator import AbstractFingerprintGenerator, MD5FingerprintGenerator, \
SSHKeyGenFingerprintGenerator, KeyFingerprint
from op_askpass.fingerprint_generator import (
AbstractFingerprintGenerator,
MD5FingerprintGenerator,
SSHKeyGenFingerprintGenerator,
KeyFingerprint,
)
class AbstractKeyLoader(abc.ABC):
@abc.abstractmethod
def list_loaded_keys(self) -> List[KeyFingerprint]: ...
def list_loaded_keys(self) -> List[KeyFingerprint]:
...
@abc.abstractmethod
def load_key(self, key_path: Path, op_domain: str, op_session_key: str, op_uid: str) -> None: ...
def load_key(
self, key_path: Path, op_domain: str, op_session_key: str, op_uid: str
) -> None:
...
@abc.abstractmethod
def get_fingerprint_generator(self) -> AbstractFingerprintGenerator: ...
def get_fingerprint_generator(self) -> AbstractFingerprintGenerator:
...
class MemoryKeyLoader(AbstractKeyLoader):
......@@ -26,7 +35,9 @@ class MemoryKeyLoader(AbstractKeyLoader):
def __init__(self) -> None:
self.__keys: Set[KeyFingerprint] = set()
def load_key(self, key_path: Path, op_domain: str, op_session_key: str, op_uid: str) -> None:
def load_key(
self, key_path: Path, op_domain: str, op_session_key: str, op_uid: str
) -> None:
self.__keys.add(self.get_fingerprint_generator().for_path(key_path))
def list_loaded_keys(self) -> List[KeyFingerprint]:
......@@ -38,17 +49,20 @@ class SSHKeyLoader(AbstractKeyLoader):
return SSHKeyGenFingerprintGenerator()
def list_loaded_keys(self) -> List[KeyFingerprint]:
output: str = subprocess.check_output(
["ssh-add", "-l"],
encoding="utf-8"
)
output: str = subprocess.check_output(["ssh-add", "-l"], encoding="utf-8")
return [KeyFingerprint.from_str(s) for s in output.splitlines()]
def load_key(self, key_path: Path, op_domain: str, op_session_key: str, op_uid: str) -> None:
def load_key(
self, key_path: Path, op_domain: str, op_session_key: str, op_uid: str
) -> None:
env = {**os.environ, f"OP_SESSION_{op_domain}": op_session_key}
subprocess.check_call(
["ssh-add", str(key_path)],
env={**env, "OP_ASKPASS_ITEM_NAME": op_uid, "SSH_ASKPASS": "op-askpass-get-password"},
env={
**env,
"OP_ASKPASS_ITEM_NAME": op_uid,
"SSH_ASKPASS": "op-askpass-get-password",
},
stdin=subprocess.DEVNULL,
)
......@@ -6,7 +6,8 @@ from op_askpass.key_loader import AbstractKeyLoader, MemoryKeyLoader
class AbstractKeyLoaderTests(abc.ABC):
@abc.abstractmethod
def key_loader_factory(self) -> AbstractKeyLoader: ...
def key_loader_factory(self) -> AbstractKeyLoader:
...
def test_list_loaded_keys_should_return_empty_list_when_empty(self) -> None:
loader = self.key_loader_factory()
......@@ -20,7 +21,12 @@ class AbstractKeyLoaderTests(abc.ABC):
fingerprint_generator = loader.get_fingerprint_generator()
key_path = Path(__file__).parent / "test_data" / "test_key"
loader.load_key(key_path=key_path, op_domain="some-domain", op_uid="some-uid", op_session_key="some-key")
loader.load_key(
key_path=key_path,
op_domain="some-domain",
op_uid="some-uid",
op_session_key="some-key",
)
assert loader.list_loaded_keys() == [fingerprint_generator.for_path(key_path)]
......
......@@ -16,7 +16,9 @@ class KeyEntry:
class AbstractKeyStore(abc.ABC):
@abc.abstractmethod
def add_fingerprint(self, fingerprint: KeyFingerprint, onepass_uid: str, key_path: Path) -> None:
def add_fingerprint(
self, fingerprint: KeyFingerprint, onepass_uid: str, key_path: Path
) -> None:
...
@abc.abstractmethod
......@@ -28,7 +30,8 @@ class AbstractKeyStore(abc.ABC):
...
@abc.abstractmethod
def items(self) -> List[Tuple[KeyFingerprint, KeyEntry]]: ...
def items(self) -> List[Tuple[KeyFingerprint, KeyEntry]]:
...
class MemoryKeyStore(AbstractKeyStore):
......@@ -44,7 +47,9 @@ class MemoryKeyStore(AbstractKeyStore):
def __init__(self) -> None:
self.__store: Dict[KeyFingerprint, KeyEntry] = {}
def add_fingerprint(self, fingerprint: KeyFingerprint, onepass_uid: str, key_path: Path) -> None:
def add_fingerprint(
self, fingerprint: KeyFingerprint, onepass_uid: str, key_path: Path
) -> None:
self.__store[fingerprint] = KeyEntry(onepass_uid=onepass_uid, key_path=key_path)
......@@ -64,16 +69,36 @@ class FileKeyStore(AbstractKeyStore):
def __read_contents(file_path: Path) -> Dict[KeyFingerprint, KeyEntry]:
try:
data = json.loads(file_path.read_text(encoding="utf-8"))
return {KeyFingerprint.from_str(k): KeyEntry(onepass_uid=v["onepass_uid"], key_path=Path(v["key_path"])) for k, v in data.items()}
return {
KeyFingerprint.from_str(k): KeyEntry(
onepass_uid=v["onepass_uid"], key_path=Path(v["key_path"])
)
for k, v in data.items()
}
except IOError:
return {}
@staticmethod
def __save_contents(file_path: Path, contents: Dict[KeyFingerprint, KeyEntry]) -> None:
file_path.write_text(json.dumps({k.to_str(): {"onepass_uid": v.onepass_uid, "key_path": str(v.key_path)} for k, v in contents.items()}), encoding="utf-8")
def add_fingerprint(self, fingerprint: KeyFingerprint, onepass_uid: str, key_path: Path) -> None:
def __save_contents(
file_path: Path, contents: Dict[KeyFingerprint, KeyEntry]
) -> None:
file_path.write_text(
json.dumps(
{
k.to_str(): {
"onepass_uid": v.onepass_uid,
"key_path": str(v.key_path),
}
for k, v in contents.items()
}
),
encoding="utf-8",
)
def add_fingerprint(
self, fingerprint: KeyFingerprint, onepass_uid: str, key_path: Path
) -> None:
contents = self.__read_contents(self.__file_path)
contents[fingerprint] = KeyEntry(onepass_uid=onepass_uid, key_path=key_path)
self.__save_contents(file_path=self.__file_path, contents=contents)
......
......@@ -5,18 +5,27 @@ from pathlib import Path
import pytest
from op_askpass.fingerprint_generator import KeyFingerprint
from op_askpass.key_store import AbstractKeyStore, MemoryKeyStore, FileKeyStore, KeyEntry
from op_askpass.key_store import (
AbstractKeyStore,
MemoryKeyStore,
FileKeyStore,
KeyEntry,
)
class AbstractKeyStoreTests(abc.ABC):
def test_add_key_fingerprint_should_correctly_add_fingerprint(self) -> None:
key_store: AbstractKeyStore = self.key_store_factory()
onepass_uid = "XXXXXX"
fingerprint = KeyFingerprint(length=10, hash="SSSSSSSSS", comment="x", key_type="some")
fingerprint = KeyFingerprint(
length=10, hash="SSSSSSSSS", comment="x", key_type="some"
)
path = Path("x")
expected_entry = KeyEntry(onepass_uid=onepass_uid, key_path=path)
key_store.add_fingerprint(fingerprint=fingerprint, onepass_uid=onepass_uid, key_path=path)
key_store.add_fingerprint(
fingerprint=fingerprint, onepass_uid=onepass_uid, key_path=path
)
assert key_store.get_key_entry(fingerprint=fingerprint) == expected_entry
assert key_store.items() == [(fingerprint, expected_entry)]
......@@ -24,9 +33,13 @@ class AbstractKeyStoreTests(abc.ABC):
def test_delete_key_fingerprint_should_correctly_remove_a_fingerprint(self) -> None:
key_store: AbstractKeyStore = self.key_store_factory()
onepass_uid = "XXXXXX"
fingerprint = KeyFingerprint(length=10, hash="SSSSSSSSS", comment="x", key_type="some")
fingerprint = KeyFingerprint(
length=10, hash="SSSSSSSSS", comment="x", key_type="some"
)
path = Path("x")
key_store.add_fingerprint(fingerprint=fingerprint, onepass_uid=onepass_uid, key_path=path)
key_store.add_fingerprint(
fingerprint=fingerprint, onepass_uid=onepass_uid, key_path=path
)
key_store.delete_fingerprint(fingerprint=fingerprint)
......@@ -34,11 +47,17 @@ class AbstractKeyStoreTests(abc.ABC):
key_store.get_key_entry(fingerprint=fingerprint)
assert key_store.items() == []
def test_get_onepass_uid_should_raise_key_error_on_missing_fingerprint(self) -> None:
def test_get_onepass_uid_should_raise_key_error_on_missing_fingerprint(
self
) -> None:
key_store: AbstractKeyStore = self.key_store_factory()
with pytest.raises(KeyError, match="not-existing"):
key_store.get_key_entry(fingerprint=KeyFingerprint(length=10, hash="not-existing", comment="x", key_type="some"))
key_store.get_key_entry(
fingerprint=KeyFingerprint(
length=10, hash="not-existing", comment="x", key_type="some"
)
)
@abc.abstractmethod
def key_store_factory(self) -> AbstractKeyStore:
......
from op_askpass.operations.list_keys import list_keys
from op_askpass.operations.add_key import add_key_from_path
from op_askpass.operations.delete_key import delete_key_from_path
from op_askpass.operations.op_client import setup_op_client, login_to_op, get_password_from_op
from op_askpass.operations.op_client import (
setup_op_client,
login_to_op,
get_password_from_op,
)
......@@ -4,6 +4,13 @@ from op_askpass.fingerprint_generator import AbstractFingerprintGenerator
from op_askpass.key_store import AbstractKeyStore
def add_key_from_path(path: Path, onepass_uid: str, fingerprint_generator: AbstractFingerprintGenerator, key_store: AbstractKeyStore) -> None:
def add_key_from_path(
path: Path,
onepass_uid: str,
fingerprint_generator: AbstractFingerprintGenerator,
key_store: AbstractKeyStore,
) -> None:
fingerprint = fingerprint_generator.for_path(path=path)
key_store.add_fingerprint(fingerprint=fingerprint, onepass_uid=onepass_uid, key_path=path.absolute())
key_store.add_fingerprint(
fingerprint=fingerprint, onepass_uid=onepass_uid, key_path=path.absolute()
)
......@@ -12,6 +12,13 @@ def test_add_key_should_add_key_from_path(tmp_path: Path) -> None:
fingerprint_generator = MD5FingerprintGenerator()
uid = "some-uid"
add_key_from_path(path=key_path, onepass_uid=uid, fingerprint_generator=fingerprint_generator, key_store=key_store)
add_key_from_path(
path=key_path,
onepass_uid=uid,
fingerprint_generator=fingerprint_generator,
key_store=key_store,
)
assert key_store.get_key_entry(fingerprint=fingerprint_generator.for_path(key_path)) == KeyEntry(onepass_uid=uid, key_path=key_path)
assert key_store.get_key_entry(
fingerprint=fingerprint_generator.for_path(key_path)
) == KeyEntry(onepass_uid=uid, key_path=key_path)
......@@ -4,7 +4,10 @@ from op_askpass.fingerprint_generator import AbstractFingerprintGenerator
from op_askpass.key_store import AbstractKeyStore
def delete_key_from_path(path: Path, fingerprint_generator: AbstractFingerprintGenerator,
key_store: AbstractKeyStore) -> None:
def delete_key_from_path(
path: Path,
fingerprint_generator: AbstractFingerprintGenerator,
key_store: AbstractKeyStore,
) -> None:
fingerprint = fingerprint_generator.for_path(path=path)
key_store.delete_fingerprint(fingerprint=fingerprint)
......@@ -12,8 +12,12 @@ def test_delete_key_from_path_should_remove_the_key(tmp_path: Path) -> None:
fingerprint_generator = MD5FingerprintGenerator()
uid = "some-uid"
fingerprint = fingerprint_generator.for_path(key_path)
key_store.add_fingerprint(fingerprint=fingerprint, onepass_uid=uid, key_path=key_path)
key_store.add_fingerprint(
fingerprint=fingerprint, onepass_uid=uid, key_path=key_path
)
delete_key_from_path(path=key_path, fingerprint_generator=fingerprint_generator, key_store=key_store)
delete_key_from_path(
path=key_path, fingerprint_generator=fingerprint_generator, key_store=key_store
)
assert key_store.items() == []
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment