From 4d753a808b882a5f502ebfbbe3d7300201710554 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 7 Jul 2022 14:58:07 -0400 Subject: [PATCH 1/3] Common: Add Credentials.from_json_array() --- monkey/common/credentials/credentials.py | 6 ++++++ .../common/credentials/test_credentials.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py index e04be27a5..291d488ca 100644 --- a/monkey/common/credentials/credentials.py +++ b/monkey/common/credentials/credentials.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from dataclasses import dataclass from typing import Any, Mapping, MutableMapping, Sequence, Tuple @@ -142,3 +143,8 @@ class Credentials: @staticmethod def to_json(credentials: Credentials) -> str: return CredentialsSchema().dumps(credentials) + + @staticmethod + def from_json_array(credentials_array_json: str) -> Sequence[Credentials]: + credentials_list = json.loads(credentials_array_json) + return [Credentials.from_mapping(c) for c in credentials_list] diff --git a/monkey/tests/unit_tests/common/credentials/test_credentials.py b/monkey/tests/unit_tests/common/credentials/test_credentials.py index c68e1c813..07a6a5ad6 100644 --- a/monkey/tests/unit_tests/common/credentials/test_credentials.py +++ b/monkey/tests/unit_tests/common/credentials/test_credentials.py @@ -87,3 +87,22 @@ def test_credentials_deserialization__invalid_component(): } with pytest.raises(InvalidCredentialComponentError): Credentials.from_mapping(invalid_data) + + +def test_from_json_array(): + deserialized_credentials_0 = Credentials.from_mapping(CREDENTIALS_DICT) + deserialized_credentials_1 = Credentials( + secrets=(Password(PASSWORD),), identities=(Username("STUPID"),) + ) + deserialized_credentials_2 = Credentials(secrets=(LMHash(LM_HASH),), identities=tuple()) + credentials_array_json = ( + f"[{Credentials.to_json(deserialized_credentials_0)}," + f"{Credentials.to_json(deserialized_credentials_1)}," + f"{Credentials.to_json(deserialized_credentials_2)}]" + ) + + credentials_sequence = Credentials.from_json_array(credentials_array_json) + assert len(credentials_sequence) == 3 + assert credentials_sequence[0] == deserialized_credentials_0 + assert credentials_sequence[1] == deserialized_credentials_1 + assert credentials_sequence[2] == deserialized_credentials_2 From 6bd0e7dc3a3aeb148fab5b90fa333e2a47ed049c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 7 Jul 2022 15:08:17 -0400 Subject: [PATCH 2/3] Common: Add doctrings for Credential serialization --- monkey/common/credentials/credentials.py | 50 ++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py index 291d488ca..34ee346fb 100644 --- a/monkey/common/credentials/credentials.py +++ b/monkey/common/credentials/credentials.py @@ -122,6 +122,17 @@ class Credentials: @staticmethod def from_mapping(credentials: Mapping) -> Credentials: + """ + Construct a Credentials object from a Mapping + + :param credentials: A mapping that represents a Credentials object + :return: A Credentials object + :raises InvalidCredentialsError: If the provided Mapping does not represent a valid + Credentials object + :raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets` + are not a valid ICredentialComponent + """ + try: deserialized_data = CredentialsSchema().load(credentials) return Credentials(**deserialized_data) @@ -132,6 +143,17 @@ class Credentials: @staticmethod def from_json(credentials: str) -> Credentials: + """ + Construct a Credentials object from a JSON string + + :param credentials: A JSON string that represents a Credentials object + :return: A Credentials object + :raises InvalidCredentialsError: If the provided JSON does not represent a valid + Credentials object + :raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets` + are not a valid ICredentialComponent + """ + try: deserialized_data = CredentialsSchema().loads(credentials) return Credentials(**deserialized_data) @@ -140,11 +162,31 @@ class Credentials: except MarshmallowError as err: raise InvalidCredentialsError(str(err)) - @staticmethod - def to_json(credentials: Credentials) -> str: - return CredentialsSchema().dumps(credentials) - @staticmethod def from_json_array(credentials_array_json: str) -> Sequence[Credentials]: + """ + Construct a sequence of Credentials object from a JSON string + + :param credentials: A JSON string that represents an array of Credentials objects + :return: A Sequence of Credentials objects + :raises InvalidCredentialsError: If the provided JSON does not represent a valid + Credentials object + :raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets` + are not a valid ICredentialComponent + :raises JSONDecodeError: If the provided string is not valid JSON + :raises TypeError: If the provided JSON does not represent an array + """ + credentials_list = json.loads(credentials_array_json) return [Credentials.from_mapping(c) for c in credentials_list] + + @staticmethod + def to_json(credentials: Credentials) -> str: + """ + Serialize a Credentials object to JSON + + :param credentials: A Credentials objcet + :return: A JSON string representing a Credentials object + """ + + return CredentialsSchema().dumps(credentials) From 4edb4f697173cdd6175dfa4a1cf34c0dce21d99f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 7 Jul 2022 15:26:17 -0400 Subject: [PATCH 3/3] Common: Add Credentials.to_json_array() --- monkey/common/credentials/credentials.py | 12 +++++- .../common/credentials/test_credentials.py | 41 ++++++++++++------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py index 34ee346fb..846137af1 100644 --- a/monkey/common/credentials/credentials.py +++ b/monkey/common/credentials/credentials.py @@ -185,8 +185,18 @@ class Credentials: """ Serialize a Credentials object to JSON - :param credentials: A Credentials objcet + :param credentials: A Credentials object :return: A JSON string representing a Credentials object """ return CredentialsSchema().dumps(credentials) + + @staticmethod + def to_json_array(credentials: Sequence[Credentials]) -> str: + """ + Serialize a Sequence of Credentials objects to a JSON array + + :param credentials: A Sequence of Credentials objects + :return: A JSON string representing an array of Credentials objects + """ + return "[" + ",".join([Credentials.to_json(c) for c in credentials]) + "]" diff --git a/monkey/tests/unit_tests/common/credentials/test_credentials.py b/monkey/tests/unit_tests/common/credentials/test_credentials.py index 07a6a5ad6..def11896b 100644 --- a/monkey/tests/unit_tests/common/credentials/test_credentials.py +++ b/monkey/tests/unit_tests/common/credentials/test_credentials.py @@ -89,20 +89,33 @@ def test_credentials_deserialization__invalid_component(): Credentials.from_mapping(invalid_data) +DESERIALIZED_CREDENTIALS_0 = Credentials.from_mapping(CREDENTIALS_DICT) +DESERIALIZED_CREDENTIALS_1 = Credentials( + secrets=(Password(PASSWORD),), identities=(Username("STUPID"),) +) +DESERIALIZED_CREDENTIALS_2 = Credentials(secrets=(LMHash(LM_HASH),), identities=tuple()) +credentials_array_json = ( + f"[{Credentials.to_json(DESERIALIZED_CREDENTIALS_0)}," + f"{Credentials.to_json(DESERIALIZED_CREDENTIALS_1)}," + f"{Credentials.to_json(DESERIALIZED_CREDENTIALS_2)}]" +) + + def test_from_json_array(): - deserialized_credentials_0 = Credentials.from_mapping(CREDENTIALS_DICT) - deserialized_credentials_1 = Credentials( - secrets=(Password(PASSWORD),), identities=(Username("STUPID"),) - ) - deserialized_credentials_2 = Credentials(secrets=(LMHash(LM_HASH),), identities=tuple()) - credentials_array_json = ( - f"[{Credentials.to_json(deserialized_credentials_0)}," - f"{Credentials.to_json(deserialized_credentials_1)}," - f"{Credentials.to_json(deserialized_credentials_2)}]" + credentials_sequence = Credentials.from_json_array(credentials_array_json) + + assert len(credentials_sequence) == 3 + assert credentials_sequence[0] == DESERIALIZED_CREDENTIALS_0 + assert credentials_sequence[1] == DESERIALIZED_CREDENTIALS_1 + assert credentials_sequence[2] == DESERIALIZED_CREDENTIALS_2 + + +def test_to_json_array(): + expected_credentials_dict = json.loads(credentials_array_json) + actual_credentials_dict = json.loads( + Credentials.to_json_array( + [DESERIALIZED_CREDENTIALS_0, DESERIALIZED_CREDENTIALS_1, DESERIALIZED_CREDENTIALS_2] + ) ) - credentials_sequence = Credentials.from_json_array(credentials_array_json) - assert len(credentials_sequence) == 3 - assert credentials_sequence[0] == deserialized_credentials_0 - assert credentials_sequence[1] == deserialized_credentials_1 - assert credentials_sequence[2] == deserialized_credentials_2 + assert actual_credentials_dict == expected_credentials_dict