diff --git a/src/main/python/covata/delta/crypto/cryptoservice.py b/src/main/python/covata/delta/crypto/cryptoservice.py index 7126fe0..f480f04 100644 --- a/src/main/python/covata/delta/crypto/cryptoservice.py +++ b/src/main/python/covata/delta/crypto/cryptoservice.py @@ -53,7 +53,6 @@ def save(self, private_key, file_name): >>> crypto_service.save(private_key, identity_id + ".signing.pem") - :param private_key: the private key object :type private_key: :class:`RSAPrivateKey` :param str file_name: the name of the .pem file to be written @@ -69,6 +68,12 @@ def save(self, private_key, file_name): self.logger.debug("creating directory %s", self.key_store_path) os.makedirs(self.key_store_path) + if os.path.isfile(file_path): + msg = "Save failed: A key with name [{}] exists in keystore".format( + file_name) + self.logger.error(msg) + raise IOError(msg) + with open(file_path, 'w') as f: self.logger.debug("Saving %s", file_name) f.write(pem.decode(encoding='utf8')) diff --git a/src/main/python/covata/delta/crypto/signer.py b/src/main/python/covata/delta/crypto/signer.py index cd01689..4a9d70a 100644 --- a/src/main/python/covata/delta/crypto/signer.py +++ b/src/main/python/covata/delta/crypto/signer.py @@ -59,16 +59,15 @@ class CVTSigner(AuthBase, LogMixin): def __init__(self, crypto_service, identity_id): """ - Create a Request Signer object to sign a - :class:`~requests.Request` object using - the CVT1 request signing scheme. + Creates a Request Signer object to sign a :class:`~requests.Request` + object using the CVT1 request signing scheme. - The :class:`~.CVTSigner` can be instantiated - directly using its constructor: + The :class:`~.CVTSigner` can be instantiated directly using its + constructor: >>> signer = CVTSigner(crypto_service, authorizing_identity) - The :class:`~.CVTSigner` can also be instantiated indirectly via a + It can also be instantiated indirectly via a :class:`~.CryptoService` object by calling :func:`~covata.delta.crypto.CryptoService.signer`: @@ -85,8 +84,9 @@ def __init__(self, crypto_service, identity_id): ... auth=crypto_service.signer(requestor_id)) >>> print(response.json()) - It is also possible to invoke the call to manually attach - the appropriate headers to a :class:`~requests.PreparedRequest` object: + It is also possible to invoke the :func:`~.CVTSigner.__call__` + manually to attach the appropriate headers to a + :class:`~requests.PreparedRequest` object: >>> prepared_request = request.prepare() >>> signer(prepared_request) @@ -118,17 +118,13 @@ def __get_auth_header(self, request): self.logger.debug(string_to_sign) signature = b64encode(self.__sign(string_to_sign)).decode('utf-8') - auth_header = "{algorithm} Identity={identity_id}, " \ - "SignedHeaders={signed_headers}, Signature={signature}" \ + return "{algorithm} Identity={identity_id}, " \ + "SignedHeaders={signed_headers}, Signature={signature}" \ .format(algorithm=self.SIGNING_ALGORITHM, identity_id=self.__identity_id, signed_headers=signature_materials.signed_headers, signature=signature) - self.logger.debug(auth_header) - - return auth_header - def __sign(self, string_to_sign): private_key = self.__crypto_service.load( self.__identity_id + ".signing.pem") @@ -147,13 +143,6 @@ def __get_hashed_payload(self, payload): def __get_materials(self, request): # type: (PreparedRequest) -> SignatureMaterial - """ - prepare the signature materials needed - - :param request: the prepared request by - :return: the SignatureMaterial named tuple - :rtype: :class: `SignatureMaterial` - """ # /master/identities/a123?key=an+arbitrary+value&key2=x path = request.path_url.split("?") uri = self.__encode_uri("/".join(path[0].split("/")[2:])) diff --git a/src/main/python/covata/delta/util.py b/src/main/python/covata/delta/util.py index fc0c39e..b5c1608 100644 --- a/src/main/python/covata/delta/util.py +++ b/src/main/python/covata/delta/util.py @@ -13,6 +13,7 @@ # limitations under the License. import logging +import inspect __all__ = ["LogMixin"] @@ -20,4 +21,24 @@ class LogMixin(object): @property def logger(self): - return logging.getLogger(self.__class__.__name__) + return logging.getLogger(self.__caller()) + + def __caller(self): + """ + Gets the name of the caller in {package}.{module}.{class} format + + :return: the + """ + # type: () -> str + stack = inspect.stack() + if len(stack) < 2: + return '' + + caller_frame = stack[2][0] + module = inspect.getmodule(caller_frame) + name = filter(lambda x: x is not None, [ + module.__name__ if module else None, + self.__class__.__name__]) + + del caller_frame + return ".".join(name) diff --git a/src/unittest/python/conftest.py b/src/unittest/python/conftest.py index 7eedc11..77d35a2 100644 --- a/src/unittest/python/conftest.py +++ b/src/unittest/python/conftest.py @@ -23,14 +23,14 @@ import covata.delta.api as api -@pytest.yield_fixture(scope="session") +@pytest.yield_fixture(scope="function") def temp_directory(): directory = tempfile.mkdtemp() yield directory shutil.rmtree(directory) -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def crypto_service(temp_directory): return crypto.CryptoService(temp_directory, b"passphrase") @@ -63,6 +63,6 @@ def mock_signer(mocker, crypto_service): return_value=mocker.Mock()) -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def api_client(crypto_service): return api.RequestsApiClient(crypto_service) diff --git a/src/unittest/python/test_crypto_service.py b/src/unittest/python/test_crypto_service.py index ea7a747..0b88098 100644 --- a/src/unittest/python/test_crypto_service.py +++ b/src/unittest/python/test_crypto_service.py @@ -14,9 +14,10 @@ import base64 +import pytest +import requests from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa -import requests def test_generate_key_pairs(crypto_service): @@ -66,3 +67,11 @@ def test_construct_signer(mocker, crypto_service, private_key): json=dict(content="abcd")) signer(r.prepare()) load.assert_called_once_with("mock.signing.pem") + + +def test_save__should__fail_when_key_exists(crypto_service, private_key): + crypto_service.save(private_key, "mock.pem") + with pytest.raises(IOError) as excinfo: + crypto_service.save(private_key, "mock.pem") + expected = "Save failed: A key with name [mock.pem] exists in keystore" + assert expected in str(excinfo.value)