diff --git a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py index 88219cef2..351de3c32 100644 --- a/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py +++ b/envs/monkey_zoo/blackbox/island_client/monkey_island_client.py @@ -1,7 +1,7 @@ import json import logging import time -from typing import Sequence, Union +from typing import List, Sequence, Union from bson import json_util @@ -30,7 +30,7 @@ class MonkeyIslandClient(object): def get_propagation_credentials(self) -> Sequence[Credentials]: response = self.requests.get("api/propagation-credentials") - return [Credentials.from_mapping(credentials) for credentials in response.json()] + return [Credentials(**credentials) for credentials in response.json()] @avoid_race_condition def import_config(self, test_configuration: TestConfiguration): @@ -59,9 +59,9 @@ class MonkeyIslandClient(object): assert False @avoid_race_condition - def _import_credentials(self, propagation_credentials: Credentials): + def _import_credentials(self, propagation_credentials: List[Credentials]): serialized_propagation_credentials = [ - Credentials.to_mapping(credentials) for credentials in propagation_credentials + credentials.dict(simplify=True) for credentials in propagation_credentials ] response = self.requests.put_json( "/api/propagation-credentials/configured-credentials", diff --git a/monkey/common/credentials/__init__.py b/monkey/common/credentials/__init__.py index 6275e0985..66b91971d 100644 --- a/monkey/common/credentials/__init__.py +++ b/monkey/common/credentials/__init__.py @@ -1,8 +1,3 @@ -from .credential_component_type import CredentialComponentType -from .i_credential_component import ICredentialComponent - -from .validators import InvalidCredentialComponentError, InvalidCredentialsError - from .lm_hash import LMHash from .nt_hash import NTHash from .password import Password diff --git a/monkey/common/credentials/credential_component_schema.py b/monkey/common/credentials/credential_component_schema.py deleted file mode 100644 index ff3e657be..000000000 --- a/monkey/common/credentials/credential_component_schema.py +++ /dev/null @@ -1,20 +0,0 @@ -from marshmallow import Schema, post_load, validate -from marshmallow_enum import EnumField - -from common.utils.code_utils import del_key - -from . import CredentialComponentType - - -class CredentialTypeField(EnumField): - def __init__(self, credential_component_type: CredentialComponentType): - super().__init__( - CredentialComponentType, validate=validate.Equal(credential_component_type) - ) - - -class CredentialComponentSchema(Schema): - @post_load - def _strip_credential_type(self, data, **kwargs): - del_key(data, "credential_type") - return data diff --git a/monkey/common/credentials/credential_component_type.py b/monkey/common/credentials/credential_component_type.py deleted file mode 100644 index 25bd3a168..000000000 --- a/monkey/common/credentials/credential_component_type.py +++ /dev/null @@ -1,9 +0,0 @@ -from enum import Enum, auto - - -class CredentialComponentType(Enum): - USERNAME = auto() - PASSWORD = auto() - NT_HASH = auto() - LM_HASH = auto() - SSH_KEYPAIR = auto() diff --git a/monkey/common/credentials/credentials.py b/monkey/common/credentials/credentials.py index 93390f212..dcd011d01 100644 --- a/monkey/common/credentials/credentials.py +++ b/monkey/common/credentials/credentials.py @@ -1,190 +1,35 @@ from __future__ import annotations -from dataclasses import dataclass -from typing import Any, Mapping, Optional, Type +from typing import Optional, Union -from marshmallow import Schema, fields, post_load, pre_dump -from marshmallow.exceptions import MarshmallowError +from pydantic import SecretBytes, SecretStr -from ..utils import IJSONSerializable -from . import ( - CredentialComponentType, - InvalidCredentialComponentError, - InvalidCredentialsError, - LMHash, - NTHash, - Password, - SSHKeypair, - Username, -) -from .i_credential_component import ICredentialComponent -from .lm_hash import LMHashSchema -from .nt_hash import NTHashSchema -from .password import PasswordSchema -from .ssh_keypair import SSHKeypairSchema -from .username import UsernameSchema +from ..base_models import InfectionMonkeyBaseModel +from . import LMHash, NTHash, Password, SSHKeypair, Username -CREDENTIAL_COMPONENT_TYPE_TO_CLASS: Mapping[CredentialComponentType, Type[ICredentialComponent]] = { - CredentialComponentType.LM_HASH: LMHash, - CredentialComponentType.NT_HASH: NTHash, - CredentialComponentType.PASSWORD: Password, - CredentialComponentType.SSH_KEYPAIR: SSHKeypair, - CredentialComponentType.USERNAME: Username, -} - -CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA: Mapping[CredentialComponentType, Schema] = { - CredentialComponentType.LM_HASH: LMHashSchema(), - CredentialComponentType.NT_HASH: NTHashSchema(), - CredentialComponentType.PASSWORD: PasswordSchema(), - CredentialComponentType.SSH_KEYPAIR: SSHKeypairSchema(), - CredentialComponentType.USERNAME: UsernameSchema(), -} - -CredentialComponentMapping = Optional[Mapping[str, Any]] -CredentialsMapping = Mapping[str, CredentialComponentMapping] +Secret = Union[Password, LMHash, NTHash, SSHKeypair] +Identity = Username -class CredentialsSchema(Schema): - identity = fields.Mapping(allow_none=True) - secret = fields.Mapping(allow_none=True) +def get_plaintext(secret: Union[SecretStr, SecretBytes, None, str]) -> Optional[str]: + if isinstance(secret, (SecretStr, SecretBytes)): + return secret.get_secret_value() + else: + return secret - @post_load - def _make_credentials( - self, - credentials: CredentialsMapping, - **kwargs: Mapping[str, Any], - ) -> Mapping[str, Optional[ICredentialComponent]]: - if not any(credentials.values()): - raise InvalidCredentialsError("At least one credentials component must be defined") - return { - key: CredentialsSchema._build_credential_component(credential_component_mapping) - for key, credential_component_mapping in credentials.items() +class Credentials(InfectionMonkeyBaseModel): + """Represents a credential pair (an identity and a secret)""" + + identity: Optional[Identity] + """Identity part of credentials, like a username or an email""" + + secret: Optional[Secret] + """Secret part of credentials, like a password or a hash""" + + class Config: + json_encoders = { + # This makes secrets dumpable to json, but not loggable + SecretStr: get_plaintext, + SecretBytes: get_plaintext, } - - @staticmethod - def _build_credential_component( - credential_component: CredentialComponentMapping, - ) -> Optional[ICredentialComponent]: - if credential_component is None: - return None - - try: - credential_component_type = CredentialComponentType[ - credential_component["credential_type"] - ] - except KeyError as err: - raise InvalidCredentialsError(f"Unknown credential component type {err}") - - credential_component_class = CREDENTIAL_COMPONENT_TYPE_TO_CLASS[credential_component_type] - credential_component_schema = CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA[ - credential_component_type - ] - - try: - return credential_component_class( - **credential_component_schema.load(credential_component) - ) - except MarshmallowError as err: - raise InvalidCredentialComponentError(credential_component_class, str(err)) - - @pre_dump - def _serialize_credentials(self, credentials: Credentials, **kwargs) -> CredentialsMapping: - return { - "identity": CredentialsSchema._serialize_credential_component(credentials.identity), - "secret": CredentialsSchema._serialize_credential_component(credentials.secret), - } - - @staticmethod - def _serialize_credential_component( - credential_component: Optional[ICredentialComponent], - ) -> CredentialComponentMapping: - if credential_component is None: - return None - - credential_component_schema = CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA[ - credential_component.credential_type - ] - - return credential_component_schema.dump(credential_component) - - -@dataclass(frozen=True) -class Credentials(IJSONSerializable): - identity: Optional[ICredentialComponent] - secret: Optional[ICredentialComponent] - - def __post_init__(self): - schema = CredentialsSchema() - try: - serialized_data = schema.dump(self) - - # This will raise an exception if the object is invalid. Calling this in __post__init() - # makes it impossible to construct an invalid object - schema.load(serialized_data) - except Exception as err: - raise InvalidCredentialsError(err) - - @staticmethod - def from_mapping(credentials: CredentialsMapping) -> 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) - except (InvalidCredentialsError, InvalidCredentialComponentError) as err: - raise err - except MarshmallowError as err: - raise InvalidCredentialsError(str(err)) - - @classmethod - def from_json(cls, 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) - except (InvalidCredentialsError, InvalidCredentialComponentError) as err: - raise err - except MarshmallowError as err: - raise InvalidCredentialsError(str(err)) - - @staticmethod - def to_mapping(credentials: Credentials) -> CredentialsMapping: - """ - Serialize a Credentials object to a Mapping - - :param credentials: A Credentials object - :return: A mapping representing a Credentials object - """ - - return CredentialsSchema().dump(credentials) - - @classmethod - def to_json(cls, credentials: Credentials) -> str: - """ - Serialize a Credentials object to JSON - - :param credentials: A Credentials object - :return: A JSON string representing a Credentials object - """ - - return CredentialsSchema().dumps(credentials) diff --git a/monkey/common/credentials/i_credential_component.py b/monkey/common/credentials/i_credential_component.py deleted file mode 100644 index 03dc75604..000000000 --- a/monkey/common/credentials/i_credential_component.py +++ /dev/null @@ -1,10 +0,0 @@ -from abc import ABC, abstractmethod - -from . import CredentialComponentType - - -class ICredentialComponent(ABC): - @property - @abstractmethod - def credential_type(self) -> CredentialComponentType: - pass diff --git a/monkey/common/credentials/lm_hash.py b/monkey/common/credentials/lm_hash.py index 5a04e8bae..2bace0c84 100644 --- a/monkey/common/credentials/lm_hash.py +++ b/monkey/common/credentials/lm_hash.py @@ -1,23 +1,16 @@ -from dataclasses import dataclass, field +import re -from marshmallow import fields +from pydantic import SecretStr, validator -from . import CredentialComponentType, ICredentialComponent -from .credential_component_schema import CredentialComponentSchema, CredentialTypeField -from .validators import credential_component_validator, ntlm_hash_validator +from ..base_models import InfectionMonkeyBaseModel +from .validators import ntlm_hash_regex -class LMHashSchema(CredentialComponentSchema): - credential_type = CredentialTypeField(CredentialComponentType.LM_HASH) - lm_hash = fields.Str(validate=ntlm_hash_validator) +class LMHash(InfectionMonkeyBaseModel): + lm_hash: SecretStr - -@dataclass(frozen=True) -class LMHash(ICredentialComponent): - credential_type: CredentialComponentType = field( - default=CredentialComponentType.LM_HASH, init=False - ) - lm_hash: str - - def __post_init__(self): - credential_component_validator(LMHashSchema(), self) + @validator("lm_hash") + def validate_hash_format(cls, lm_hash): + if not re.match(ntlm_hash_regex, lm_hash.get_secret_value()): + raise ValueError("Invalid LM hash provided") + return lm_hash diff --git a/monkey/common/credentials/nt_hash.py b/monkey/common/credentials/nt_hash.py index a7145a5a0..5c901fa9b 100644 --- a/monkey/common/credentials/nt_hash.py +++ b/monkey/common/credentials/nt_hash.py @@ -1,23 +1,16 @@ -from dataclasses import dataclass, field +import re -from marshmallow import fields +from pydantic import SecretStr, validator -from . import CredentialComponentType, ICredentialComponent -from .credential_component_schema import CredentialComponentSchema, CredentialTypeField -from .validators import credential_component_validator, ntlm_hash_validator +from ..base_models import InfectionMonkeyBaseModel +from .validators import ntlm_hash_regex -class NTHashSchema(CredentialComponentSchema): - credential_type = CredentialTypeField(CredentialComponentType.NT_HASH) - nt_hash = fields.Str(validate=ntlm_hash_validator) +class NTHash(InfectionMonkeyBaseModel): + nt_hash: SecretStr - -@dataclass(frozen=True) -class NTHash(ICredentialComponent): - credential_type: CredentialComponentType = field( - default=CredentialComponentType.NT_HASH, init=False - ) - nt_hash: str - - def __post_init__(self): - credential_component_validator(NTHashSchema(), self) + @validator("nt_hash") + def validate_hash_format(cls, nt_hash): + if not re.match(ntlm_hash_regex, nt_hash.get_secret_value()): + raise ValueError("Invalid NT hash provided") + return nt_hash diff --git a/monkey/common/credentials/password.py b/monkey/common/credentials/password.py index b7bd1b84c..1745de170 100644 --- a/monkey/common/credentials/password.py +++ b/monkey/common/credentials/password.py @@ -1,19 +1,7 @@ -from dataclasses import dataclass, field +from pydantic import SecretStr -from marshmallow import fields - -from . import CredentialComponentType, ICredentialComponent -from .credential_component_schema import CredentialComponentSchema, CredentialTypeField +from ..base_models import InfectionMonkeyBaseModel -class PasswordSchema(CredentialComponentSchema): - credential_type = CredentialTypeField(CredentialComponentType.PASSWORD) - password = fields.Str() - - -@dataclass(frozen=True) -class Password(ICredentialComponent): - credential_type: CredentialComponentType = field( - default=CredentialComponentType.PASSWORD, init=False - ) - password: str +class Password(InfectionMonkeyBaseModel): + password: SecretStr diff --git a/monkey/common/credentials/ssh_keypair.py b/monkey/common/credentials/ssh_keypair.py index 6b8dcded2..651716965 100644 --- a/monkey/common/credentials/ssh_keypair.py +++ b/monkey/common/credentials/ssh_keypair.py @@ -1,23 +1,8 @@ -from dataclasses import dataclass, field +from pydantic import SecretStr -from marshmallow import fields - -from . import CredentialComponentType, ICredentialComponent -from .credential_component_schema import CredentialComponentSchema, CredentialTypeField +from ..base_models import InfectionMonkeyBaseModel -class SSHKeypairSchema(CredentialComponentSchema): - credential_type = CredentialTypeField(CredentialComponentType.SSH_KEYPAIR) - # TODO: Find a list of valid formats for ssh keys and add validators. - # See https://github.com/nemchik/ssh-key-regex - private_key = fields.Str() - public_key = fields.Str() - - -@dataclass(frozen=True) -class SSHKeypair(ICredentialComponent): - credential_type: CredentialComponentType = field( - default=CredentialComponentType.SSH_KEYPAIR, init=False - ) - private_key: str +class SSHKeypair(InfectionMonkeyBaseModel): + private_key: SecretStr public_key: str diff --git a/monkey/common/credentials/username.py b/monkey/common/credentials/username.py index 86fde05ff..2bfc0b25d 100644 --- a/monkey/common/credentials/username.py +++ b/monkey/common/credentials/username.py @@ -1,19 +1,5 @@ -from dataclasses import dataclass, field - -from marshmallow import fields - -from . import CredentialComponentType, ICredentialComponent -from .credential_component_schema import CredentialComponentSchema, CredentialTypeField +from ..base_models import InfectionMonkeyBaseModel -class UsernameSchema(CredentialComponentSchema): - credential_type = CredentialTypeField(CredentialComponentType.USERNAME) - username = fields.Str() - - -@dataclass(frozen=True) -class Username(ICredentialComponent): - credential_type: CredentialComponentType = field( - default=CredentialComponentType.USERNAME, init=False - ) +class Username(InfectionMonkeyBaseModel): username: str diff --git a/monkey/common/credentials/validators.py b/monkey/common/credentials/validators.py index 2e0e2e93c..1d94575d4 100644 --- a/monkey/common/credentials/validators.py +++ b/monkey/common/credentials/validators.py @@ -1,50 +1,3 @@ import re -from typing import Type -from marshmallow import Schema, validate - -from . import ICredentialComponent - -_ntlm_hash_regex = re.compile(r"^[a-fA-F0-9]{32}$") -ntlm_hash_validator = validate.Regexp(regex=_ntlm_hash_regex) - - -class InvalidCredentialComponentError(Exception): - def __init__(self, credential_component_class: Type[ICredentialComponent], message: str): - self._credential_component_name = credential_component_class.__name__ - self._message = message - - def __str__(self) -> str: - return ( - f"Cannot construct a {self._credential_component_name} object with the supplied, " - f"invalid data: {self._message}" - ) - - -class InvalidCredentialsError(Exception): - def __init__(self, message: str): - self._message = message - - def __str__(self) -> str: - return ( - f"Cannot construct a Credentials object with the supplied, " - f"invalid data: {self._message}" - ) - - -def credential_component_validator(schema: Schema, credential_component: ICredentialComponent): - """ - Validate a credential component - - :param schema: A marshmallow schema used for validating the component - :param credential_component: A credential component to be validated - :raises InvalidCredentialComponent: if the credential_component contains invalid data - """ - try: - serialized_data = schema.dump(credential_component) - - # This will raise an exception if the object is invalid. Calling this in __post__init() - # makes it impossible to construct an invalid object - schema.load(serialized_data) - except Exception as err: - raise InvalidCredentialComponentError(credential_component.__class__, err) +ntlm_hash_regex = re.compile(r"^[a-fA-F0-9]{32}$") diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index 4bc8564ce..36879a9ca 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "aiosmb": { "hashes": [ - "sha256:0afa901093f0ad91d0b8421dec66c80bd2e9cb237a8da405984413a5d7475398", - "sha256:0e98390ba00fdc4190e698f184dfcf72b02b592cdfe9274e03cc7316ac4ee368" + "sha256:2668d63ae6e6ca30a999696c444d4afc349f85a872a204994394aa6abbf5673d", + "sha256:b0b0e7068e757b8d3a8e4be2ebf2b499933d9fa7853bc3a8e198d6a57dc77131" ], "markers": "python_version >= '3.7'", - "version": "==0.3.8" + "version": "==0.4.0" }, "aiowinreg": { "hashes": [ @@ -46,13 +46,21 @@ ], "version": "==1.5.1" }, + "asyauth": { + "hashes": [ + "sha256:a7a7998dcfa672ebae9837ba4996df237412a7f335c0f8291bb14091d0e6d19a", + "sha256:f20c497b02d17f25298d3701fb65c9a337893b68a9c7dddecc09ab956b09f828" + ], + "markers": "python_version >= '3.7'", + "version": "==0.0.2" + }, "asysocks": { "hashes": [ - "sha256:23d5fcfae71a75826c3ed787bd9b1bc3b189ec37658961bce83c9e99455e354c", - "sha256:731eda25d41783c5243153d3cb4f9357fef337c7317135488afab9ecd6b7f1a1" + "sha256:8f4516088ebec7f08d8c549e5e75e7a86b41e6043af920ba4895cf3f6c654150", + "sha256:d4c619c9d2e8be0cbdd21fa635a7eaf5886809edc948e2f76625b33b3cccfc47" ], "markers": "python_version >= '3.6'", - "version": "==0.1.7" + "version": "==0.2.1" }, "attrs": { "hashes": [ @@ -71,20 +79,21 @@ }, "bcrypt": { "hashes": [ - "sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521", - "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb", - "sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e", - "sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26", - "sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a", - "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e", - "sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa", - "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", - "sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb", - "sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40", - "sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa" + "sha256:0b0f0c7141622a31e9734b7f649451147c04ebb5122327ac0bd23744df84be90", + "sha256:1c3334446fac200499e8bc04a530ce3cf0b3d7151e0e4ac5c0dddd3d95e97843", + "sha256:2d0dd19aad87e4ab882ef1d12df505f4c52b28b69666ce83c528f42c07379227", + "sha256:594780b364fb45f2634c46ec8d3e61c1c0f1811c4f2da60e8eb15594ecbf93ed", + "sha256:7c7dd6c1f05bf89e65261d97ac3a6520f34c2acb369afb57e3ea4449be6ff8fd", + "sha256:845b1daf4df2dd94d2fdbc9454953ca9dd0e12970a0bfc9f3dcc6faea3fa96e4", + "sha256:8780e69f9deec9d60f947b169507d2c9816e4f11548f1f7ebee2af38b9b22ae4", + "sha256:bf413f2a9b0a2950fc750998899013f2e718d20fa4a58b85ca50b6df5ed1bbf9", + "sha256:bfb67f6a6c72dfb0a02f3df51550aa1862708e55128b22543e2b42c74f3620d7", + "sha256:c59c170fc9225faad04dde1ba61d85b413946e8ce2e5f5f5ff30dfd67283f319", + "sha256:dc6ec3dc19b1c193b2f7cf279d3e32e7caf447532fbcb7af0906fe4398900c33", + "sha256:ede0f506554571c8eda80db22b83c139303ec6b595b8f60c4c8157bdd0bdee36" ], "markers": "python_version >= '3.6'", - "version": "==3.2.2" + "version": "==4.0.0" }, "certifi": { "hashes": [ @@ -173,11 +182,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", - "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "version": "==2.1.1" }, "click": { "hashes": [ @@ -204,47 +213,51 @@ }, "cryptography": { "hashes": [ - "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", - "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", - "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", - "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", - "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", - "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", - "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", - "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", - "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", - "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", - "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", - "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", - "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", - "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", - "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", - "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", - "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", - "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", - "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", - "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", - "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", - "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" + "sha256:03363b92b6d76e4292ace708dcc8fd31b28ce325714f5af9f72afca6e7ac4070", + "sha256:144c9b09135caf6580fb8bf03594760a351dcb1bb6df3a4ed871527a90597e31", + "sha256:18253e7aeb7a6a12d1af7548a70349ab41da099addc0bfdc8f980c0a55624206", + "sha256:24d4137f3118900db02a2ec9a585d6dec2e79697fc90e84f19e5462dd1eeca44", + "sha256:2cbd0989e8b2bc7182f4eaa85145e1264a139da94b78adf9440be0d9ccce8898", + "sha256:3f1b0207dd0ec3167d5faec10d72368b7e1ba9eb2e3d54718963f6319254102b", + "sha256:634f38529aac216844df4df8632cdc1d7fff11c1ba2440c7299c38c1aa7a3e19", + "sha256:684134063c108b6a8af59c1869e09400a1c078336e79fa8cc1cbfb82e67d992a", + "sha256:7208cf3fa37e4c2d3ec9b49ac7721c457b793c64d548c4f7f2989ded0405f700", + "sha256:723581cbcfe89b83f86ed710b591520b6b148186b555dad2f84f717ad136f9e5", + "sha256:7ac685fe7c1103f5802e52a49f90f010dd768fd1cffd797fe01df2c674f3862a", + "sha256:7ed89815ea3483f92d6ca934ca8b2a8c35579453c1e2472a960a5086996981dd", + "sha256:88fbfc4703987ed17990a0a5b6f465447e8996cc4c4117db08afce9d0f75ff82", + "sha256:8fe3f1df20286d50dc5ab201cce4bf5c326e813d7fa39a62ed41bc01d46cd3ab", + "sha256:959cce516bacb775fb52ff122f26e23178c76ca708d68dc353e177cb7ee6f167", + "sha256:aaba2a47f0ce5c795d7caf46ff9c9ecc5d42b8ca5cecd11c0bf03d3df4626f9c", + "sha256:b224d7e73676a3ea27cd1a726591c7984db32ceb63a801e11d80669eb9964f37", + "sha256:b69f3f316711567f03eab907713a2c076f43fc84f92a6d7ce5ffc8c26e6709bc", + "sha256:c27e541b532d13a15fe969977c55753d0f187696280ba073273efb0c733e522b", + "sha256:cec962ac6db460a22ac78c1e316c7238b6979a9e369b9b1e3810af10c7445b1e", + "sha256:d2700d1593e5a03bc32d1a9e9fda9b136bf7e67e7ae4aeefcfeac5a5090c200e", + "sha256:d89a685ce8dd2c6869e9b9b884c9e0944cc74d67ca53a5bf4cd69f262fca0c3f", + "sha256:db58dcd824d67bc7f9735f3b0f402add8b33687994f0e822cbe16868d68aa2a1", + "sha256:ebe9e75f9a759e1019bdd2a823b51c8d4002bed006d60c025e3d6b9dff33ebaa", + "sha256:efa101918a6b332e1db0d3c69085217eb754b85124ab8ca94bba9b1e8d079f84", + "sha256:ff8709fcd3e49c41bc3da5720ab39882b76af8d15e268807e6d906f8c3a252fc" ], "markers": "python_version >= '3.6'", - "version": "==37.0.4" + "version": "==38.0.0" }, "dnspython": { "hashes": [ "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" ], - "markers": "python_version >= '3.6' and python_version < '4'", + "markers": "python_version >= '3.6' and python_version < '4.0'", "version": "==2.2.1" }, "flask": { "hashes": [ - "sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5", - "sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d" + "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", + "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" ], "markers": "python_version >= '3.7'", - "version": "==2.2.1" + "version": "==2.2.2" }, "future": { "hashes": [ @@ -388,11 +401,11 @@ }, "marshmallow": { "hashes": [ - "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb", - "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7" + "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408", + "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0" ], "index": "pypi", - "version": "==3.17.0" + "version": "==3.17.1" }, "marshmallow-enum": { "hashes": [ @@ -412,19 +425,19 @@ }, "minikerberos": { "hashes": [ - "sha256:3c383f67ebcf6f28105ed54f623a6a5c677a24e3f0c9ad69ed453f77e569d714", - "sha256:789f802263fa1882f701b123f6eec048b45cd731bf1b528870005daf07402047" + "sha256:c05cfcd846b1973b2b0d501e9e9fa2a263543d7762b052fc803fc1de849286a3", + "sha256:e912eb4bea899e1875707e7998001ed1047e1b32d5d7bf74d8b6137acf9614d3" ], "markers": "python_version >= '3.6'", - "version": "==0.2.20" + "version": "==0.3.1" }, "msldap": { "hashes": [ - "sha256:ac8174ed7e0162eb64b3e9dfeff13b2e1021612d0a4b2cfc6b8e5bed7c00ffe0", - "sha256:e2c22a6e396b4d7d65d73863ed44612120e8e2570ff895b5421ddf6a350085bb" + "sha256:236eacd04b0d2886e71b2890ec6c67fc626e1b9812c93a0fe21e998697415927", + "sha256:ccb5c1f40de165141931659eb71d4bbad326665aaff7bf23dd0dccb410dfa78c" ], "markers": "python_version >= '3.7'", - "version": "==0.3.40" + "version": "==0.4.0" }, "netifaces": { "hashes": [ @@ -506,49 +519,49 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0", - "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289" + "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d", + "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148" ], "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.30" + "version": "==3.0.31" }, "psutil": { "hashes": [ - "sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685", - "sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc", - "sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36", - "sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1", - "sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329", - "sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81", - "sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de", - "sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4", - "sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574", - "sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237", - "sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22", - "sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b", - "sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0", - "sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954", - "sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021", - "sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537", - "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87", - "sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0", - "sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc", - "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af", - "sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4", - "sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453", - "sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689", - "sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8", - "sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680", - "sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e", - "sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9", - "sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b", - "sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d", - "sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2", - "sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5", - "sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676" + "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97", + "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84", + "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969", + "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf", + "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32", + "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12", + "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727", + "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06", + "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c", + "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9", + "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea", + "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8", + "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f", + "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c", + "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d", + "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339", + "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444", + "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab", + "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb", + "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b", + "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8", + "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec", + "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8", + "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d", + "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34", + "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85", + "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1", + "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9", + "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1", + "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d", + "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5", + "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c" ], "index": "pypi", - "version": "==5.9.1" + "version": "==5.9.2" }, "pyasn1": { "hashes": [ @@ -632,44 +645,45 @@ }, "pydantic": { "hashes": [ - "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44", - "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d", - "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84", - "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555", - "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7", - "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131", - "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8", - "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3", - "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56", - "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0", - "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4", - "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453", - "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044", - "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e", - "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15", - "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb", - "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001", - "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d", - "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3", - "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e", - "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f", - "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c", - "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b", - "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8", - "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567", - "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979", - "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326", - "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb", - "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f", - "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa", - "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747", - "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801", - "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55", - "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08", - "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76" + "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42", + "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624", + "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e", + "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559", + "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709", + "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9", + "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d", + "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52", + "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda", + "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912", + "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c", + "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525", + "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe", + "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41", + "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b", + "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283", + "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965", + "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c", + "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410", + "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5", + "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116", + "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98", + "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f", + "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644", + "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13", + "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd", + "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254", + "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6", + "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488", + "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5", + "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c", + "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1", + "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a", + "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2", + "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d", + "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236" ], "index": "pypi", - "version": "==1.9.2" + "version": "==1.10.2" }, "pyinstaller": { "hashes": [ @@ -690,11 +704,11 @@ }, "pyinstaller-hooks-contrib": { "hashes": [ - "sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56", - "sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001" + "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784", + "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e" ], "markers": "python_version >= '3.7'", - "version": "==2022.8" + "version": "==2022.10" }, "pymssql": { "hashes": [ @@ -795,21 +809,19 @@ }, "pyspnego": { "hashes": [ - "sha256:1ee612f20c843365fbc6cf7f95c526b4ee8795281641a9bb87083622a2f87939", - "sha256:284ca7a6218344bb90aeae02fb1d2ed73e5c991d6e4c16c0df404aeab5eb58a3", - "sha256:416fd2d67e82b44ba3d2d9062485056e4dde3c141630170e9190379d6b19972a", - "sha256:4c1be83e0aca12d64f5eec638259c77eaa8bf552c89ac69f0af2322a3be9afeb", - "sha256:4d1ea987b9c2539457235793014e0d9c5e4766da9e4e028d4b6b596cfbe53828", - "sha256:725df2030e5d1b1155bb696eca3d684f403164da8e6a6b0bee3eb02f8748f72b", - "sha256:7320539f45da463029e12f3742102085d2a0343bfe77ac550c11d2fdac1d34f5", - "sha256:77b86002082f278c3f5935d8b428a0d0659ea709e305537294ba95fc49907339", - "sha256:aa93d94775d01bf70d16840426d1ddd58c11a6a71c4d0d1d7e529ad541fa0a60", - "sha256:c2abca03b6d3c71d7ec9678c7b2220d99d9a29ef204b4c52549080169e586310", - "sha256:e6645107f200fb7bf6698512d04ea0790b292028861ce169eb97e5ad8eba14ed", - "sha256:f4784d9f8e9c50a36109d715a140150add1990fce16805a56217e8ccaf69d234" + "sha256:15cd6d3fc4d18b4b7f80259bfab1580c87dc9677d47e7cf801dad71dc23d1afc", + "sha256:210a2248060a2d789a333f7553a1a478d21812f675c507393169143cbf038d9b", + "sha256:4e967f29c099c196cbf4622587cd27e8c61f20adf78b1d3007b72596e60c9f23", + "sha256:4fab51afb757be21d543ddf78aaeb83db600a7e7daec773568db90d4b7499a2c", + "sha256:53d30afbef1255cb1a8930c14604184b07f989b6ac295a1397eac8c27fd59d8b", + "sha256:838f875ee55004a274f6470460e62b7713237ae8b66a02680a2f31e43b3b5387", + "sha256:b78a3370ace76209a52dc7816636a8c8437e323637eefe86a2193cc4ec352b3b", + "sha256:e08709c4e0838bf37d4ef8ceff2163a51abe2b071e285bb5774de5b73eab214f", + "sha256:ea8570c5363e5dd359aaf599eac6f70116e0ada734ebe557e17cc608c8bb93fc", + "sha256:fa2946ba5059f79d13cb8c47e83474de55569c16ed8f953cc47a24dda6f38f57" ], - "markers": "python_version >= '3.6'", - "version": "==0.5.3" + "markers": "python_version >= '3.7'", + "version": "==0.6.0" }, "pywin32": { "hashes": [ @@ -858,11 +870,11 @@ }, "setuptools": { "hashes": [ - "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", - "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" + "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", + "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57" ], "markers": "python_version >= '3.7'", - "version": "==63.4.1" + "version": "==65.3.0" }, "six": { "hashes": [ @@ -874,11 +886,11 @@ }, "tqdm": { "hashes": [ - "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", - "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" + "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", + "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.64.0" + "version": "==4.64.1" }, "twisted": { "extras": [ @@ -919,11 +931,11 @@ }, "unicrypto": { "hashes": [ - "sha256:0487f9dd9009c326ee9531a79412ae18ad673425a1c800d64947b96fdeb04cdf", - "sha256:fab49ee41926bb31be49552aa135f7aedc04436b49c8fe326d7b6a823810575e" + "sha256:4d1de0f0a379bb4c213302ae61d927eb8f98179bde9a0ffb8e120998a0c882a6", + "sha256:9d5dd858ad5ad608fa524987b17e8855d64d6d2450ca0ca11638f4d92fc6c80b" ], "markers": "python_version >= '3.6'", - "version": "==0.0.8" + "version": "==0.0.9" }, "urllib3": { "hashes": [ @@ -942,11 +954,11 @@ }, "werkzeug": { "hashes": [ - "sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a", - "sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a" + "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", + "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" ], "markers": "python_version >= '3.7'", - "version": "==2.2.1" + "version": "==2.2.2" }, "winacl": { "hashes": [ @@ -956,14 +968,6 @@ "markers": "python_version >= '3.6'", "version": "==0.1.3" }, - "winsspi": { - "hashes": [ - "sha256:2f5a8d2c4b9f459144426909e26a74e550512e23b6cf9af52c2a00003c7c3fdb", - "sha256:59b7c7595f91414528cfd80c6cfc77ec6f5e4e28185ebd6418f8368ddc7aca82" - ], - "markers": "python_version >= '3.6'", - "version": "==0.0.10" - }, "winsys-3.x": { "hashes": [ "sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3" @@ -976,6 +980,7 @@ "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6" ], + "index": "pypi", "markers": "sys_platform == 'win32'", "version": "==1.5.1" }, diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py index 882ced8ca..10bccade3 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py @@ -55,22 +55,22 @@ class MimikatzCredentialCollector(ICredentialCollector): identity = None if wc.username: - identity = Username(wc.username) + identity = Username(username=wc.username) if wc.password: - password = Password(wc.password) - credentials.append(Credentials(identity, password)) + password = Password(password=wc.password) + credentials.append(Credentials(identity=identity, secret=password)) if wc.lm_hash: lm_hash = LMHash(lm_hash=wc.lm_hash) - credentials.append(Credentials(identity, lm_hash)) + credentials.append(Credentials(identity=identity, secret=lm_hash)) if wc.ntlm_hash: ntlm_hash = NTHash(nt_hash=wc.ntlm_hash) - credentials.append(Credentials(identity, ntlm_hash)) + credentials.append(Credentials(identity=identity, secret=ntlm_hash)) if len(credentials) == 0 and identity is not None: - credentials.append(Credentials(identity, None)) + credentials.append(Credentials(identity=identity, secret=None)) return credentials diff --git a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py index ca271a5d8..8c1129455 100644 --- a/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py +++ b/monkey/infection_monkey/credential_collectors/ssh_collector/ssh_handler.py @@ -149,7 +149,7 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]: secret = None if info.get("name", ""): - identity = Username(info["name"]) + identity = Username(username=info["name"]) ssh_keypair = {} for key in ["public_key", "private_key"]: @@ -158,11 +158,12 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]: if len(ssh_keypair): secret = SSHKeypair( - ssh_keypair.get("private_key", ""), ssh_keypair.get("public_key", "") + private_key=ssh_keypair.get("private_key", ""), + public_key=ssh_keypair.get("public_key", ""), ) if any([identity, secret]): - ssh_credentials.append(Credentials(identity, secret)) + ssh_credentials.append(Credentials(identity=identity, secret=secret)) return ssh_credentials diff --git a/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py b/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py index 97076fbc4..f7c86c30d 100644 --- a/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py +++ b/monkey/infection_monkey/credential_repository/aggregating_propagation_credentials_repository.py @@ -1,7 +1,8 @@ import logging from typing import Any, Iterable -from common.credentials import CredentialComponentType, Credentials, ICredentialComponent +from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username +from common.credentials.credentials import Identity, Secret from infection_monkey.custom_types import PropagationCredentials from infection_monkey.i_control_channel import IControlChannel from infection_monkey.utils.decorators import request_cache @@ -43,18 +44,18 @@ class AggregatingPropagationCredentialsRepository(IPropagationCredentialsReposit if credentials.secret: self._add_secret(credentials.secret) - def _add_identity(self, identity: ICredentialComponent): - if identity.credential_type is CredentialComponentType.USERNAME: + def _add_identity(self, identity: Identity): + if isinstance(identity, Username): self._stored_credentials.setdefault("exploit_user_list", set()).add(identity.username) - def _add_secret(self, secret: ICredentialComponent): - if secret.credential_type is CredentialComponentType.PASSWORD: + def _add_secret(self, secret: Secret): + if isinstance(secret, Password): self._stored_credentials.setdefault("exploit_password_list", set()).add(secret.password) - elif secret.credential_type is CredentialComponentType.LM_HASH: + elif isinstance(secret, LMHash): self._stored_credentials.setdefault("exploit_lm_hash_list", set()).add(secret.lm_hash) - elif secret.credential_type is CredentialComponentType.NT_HASH: + elif isinstance(secret, NTHash): self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).add(secret.nt_hash) - elif secret.credential_type is CredentialComponentType.SSH_KEYPAIR: + elif isinstance(secret, SSHKeypair): self._set_attribute( "exploit_ssh_keys", [{"public_key": secret.public_key, "private_key": secret.private_key}], diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 6c155f468..b037c782a 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -6,6 +6,7 @@ from typing import Sequence, Tuple import pymssql from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT +from common.credentials.credentials import get_plaintext from common.utils.exceptions import FailedExploitationError from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_agent_dst_path @@ -111,7 +112,7 @@ class MSSQLExploiter(HostExploiter): conn = pymssql.connect( host, user, - password, + get_plaintext(password), port=port, login_timeout=self.LOGIN_TIMEOUT, timeout=self.QUERY_TIMEOUT, diff --git a/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py b/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py index df2cf65b1..0ea71c6f1 100644 --- a/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py +++ b/monkey/infection_monkey/exploit/powershell_utils/powershell_client.py @@ -11,6 +11,7 @@ from pypsrp.powershell import PowerShell, RunspacePool from typing_extensions import Protocol from urllib3 import connectionpool +from common.credentials.credentials import get_plaintext from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType @@ -42,14 +43,16 @@ def format_password(credentials: Credentials) -> Optional[str]: if credentials.secret_type == SecretType.CACHED: return None + plaintext_secret = get_plaintext(credentials.secret) + if credentials.secret_type == SecretType.PASSWORD: - return credentials.secret + return plaintext_secret if credentials.secret_type == SecretType.LM_HASH: - return f"{credentials.secret}:00000000000000000000000000000000" + return f"{plaintext_secret}:00000000000000000000000000000000" if credentials.secret_type == SecretType.NT_HASH: - return f"00000000000000000000000000000000:{credentials.secret}" + return f"00000000000000000000000000000000:{plaintext_secret}" raise ValueError(f"Unknown secret type {credentials.secret_type}") diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 80c09547f..272f150eb 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -4,6 +4,7 @@ from impacket.dcerpc.v5 import scmr, transport from impacket.dcerpc.v5.scmr import DCERPCSessionError from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT +from common.credentials.credentials import get_plaintext from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_agent_dst_path @@ -107,7 +108,14 @@ class SMBExploiter(HostExploiter): rpctransport.setRemoteHost(self.host.ip_addr) if hasattr(rpctransport, "set_credentials"): # This method exists only for selected protocol sequences. - rpctransport.set_credentials(user, password, "", lm_hash, ntlm_hash, None) + rpctransport.set_credentials( + user, + get_plaintext(password), + "", + get_plaintext(lm_hash), + get_plaintext(ntlm_hash), + None, + ) rpctransport.set_kerberos(SMBExploiter.USE_KERBEROS) scmr_rpc = rpctransport.get_dce_rpc() @@ -116,7 +124,8 @@ class SMBExploiter(HostExploiter): scmr_rpc.connect() except Exception as exc: logger.debug( - f"Can't connect to SCM on exploited machine {self.host}, port {port} : {exc}" + f"Can't connect to SCM on exploited machine {self.host}, port {port} : " + f"{exc}" ) continue diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 8b10c8c44..69b29c813 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -5,6 +5,7 @@ from pathlib import PurePath import paramiko from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT +from common.credentials.credentials import get_plaintext from common.utils import Timer from common.utils.attack_utils import ScanStatus from common.utils.exceptions import FailedExploitationError @@ -59,7 +60,7 @@ class SSHExploiter(HostExploiter): for user, ssh_key_pair in ssh_key_pairs_iterator: # Creating file-like private key for paramiko - pkey = io.StringIO(ssh_key_pair["private_key"]) + pkey = io.StringIO(get_plaintext(ssh_key_pair["private_key"])) ssh_string = "%s@%s" % (user, self.host.ip_addr) ssh = paramiko.SSHClient() diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index d90997b24..c9ac254e1 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -8,7 +8,9 @@ from typing import Optional from impacket.dcerpc.v5 import srvs, transport from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 from impacket.smbconnection import SMB_DIALECT, SMBConnection +from pydantic import SecretStr +from common.credentials.credentials import get_plaintext from common.utils.attack_utils import ScanStatus from infection_monkey.network.tools import get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem @@ -26,8 +28,8 @@ class SmbTools(object): host, agent_file: BytesIO, dst_path: PurePath, - username, - password, + username: str, + password: SecretStr, lm_hash="", ntlm_hash="", timeout=30, @@ -190,7 +192,9 @@ class SmbTools(object): return remote_full_path @staticmethod - def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeout=30): + def new_smb_connection( + host, username: str, password: SecretStr, lm_hash="", ntlm_hash="", timeout=30 + ): try: smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445) except Exception as exc: @@ -212,7 +216,13 @@ class SmbTools(object): # we know this should work because the WMI connection worked try: - smb.login(username, password, "", lm_hash, ntlm_hash) + smb.login( + username, + get_plaintext(password), + "", + get_plaintext(lm_hash), + get_plaintext(ntlm_hash), + ) except Exception as exc: logger.error(f'Error while logging into {host} using user "{username}": {exc}') return None, dialect diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index cdbb3b63f..6c5b189f7 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -5,6 +5,7 @@ import traceback from impacket.dcerpc.v5.rpcrt import DCERPCException +from common.credentials.credentials import get_plaintext from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.smb_tools import SmbTools @@ -44,7 +45,14 @@ class WmiExploiter(HostExploiter): wmi_connection = WmiTools.WmiConnection() try: - wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash) + wmi_connection.connect( + self.host, + user, + get_plaintext(password), + None, + get_plaintext(lm_hash), + get_plaintext(ntlm_hash), + ) except AccessDeniedException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) logger.debug(f"Failed connecting to {self.host} using WMI") diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index f43a61069..de19956c8 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -299,8 +299,8 @@ class ZerologonExploiter(HostExploiter): self, user: str, lmhash: str, nthash: str ) -> None: extracted_credentials = [ - Credentials(Username(user), LMHash(lmhash)), - Credentials(Username(user), NTHash(nthash)), + Credentials(identity=Username(username=user), secret=LMHash(lm_hash=lmhash)), + Credentials(identity=Username(username=user), secret=NTHash(nt_hash=nthash)), ] self.telemetry_messenger.send_telemetry(CredentialsTelem(extracted_credentials)) diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py index 28b6d7533..8c6653573 100644 --- a/monkey/infection_monkey/master/control_channel.py +++ b/monkey/infection_monkey/master/control_channel.py @@ -121,7 +121,7 @@ class ControlChannel(IControlChannel): ) response.raise_for_status() - return [Credentials.from_mapping(credentials) for credentials in response.json()] + return [Credentials(**credentials) for credentials in response.json()] except ( requests.exceptions.JSONDecodeError, requests.exceptions.ConnectionError, diff --git a/monkey/infection_monkey/ransomware/__init__.py b/monkey/infection_monkey/ransomware/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/monkey/infection_monkey/telemetry/credentials_telem.py b/monkey/infection_monkey/telemetry/credentials_telem.py index 7504b9c65..439ec7f1a 100644 --- a/monkey/infection_monkey/telemetry/credentials_telem.py +++ b/monkey/infection_monkey/telemetry/credentials_telem.py @@ -1,4 +1,3 @@ -import json from typing import Iterable from common.common_consts.telem_categories import TelemCategoryEnum @@ -24,4 +23,4 @@ class CredentialsTelem(BaseTelem): super().send(log_data=False) def get_data(self): - return [json.loads(Credentials.to_json(c)) for c in self._credentials] + return [c.dict(simplify=True) for c in self._credentials] diff --git a/monkey/infection_monkey/telemetry/telem_encoder.py b/monkey/infection_monkey/telemetry/telem_encoder.py index 3abe77439..ec060b250 100644 --- a/monkey/infection_monkey/telemetry/telem_encoder.py +++ b/monkey/infection_monkey/telemetry/telem_encoder.py @@ -1,5 +1,7 @@ import json +from pydantic import BaseModel, SecretBytes, SecretStr + from common import OperatingSystem @@ -7,4 +9,8 @@ class TelemetryJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, OperatingSystem): return obj.name + if issubclass(type(obj), BaseModel): + return obj.dict(simplify=True) + if issubclass(type(obj), (SecretStr, SecretBytes)): + return obj.get_secret_value() return json.JSONEncoder.default(self, obj) diff --git a/monkey/monkey_island/Pipfile.lock b/monkey/monkey_island/Pipfile.lock index c5c7d2c47..237a1f133 100644 --- a/monkey/monkey_island/Pipfile.lock +++ b/monkey/monkey_island/Pipfile.lock @@ -150,11 +150,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", - "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "version": "==2.1.1" }, "click": { "hashes": [ @@ -174,31 +174,35 @@ }, "cryptography": { "hashes": [ - "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", - "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", - "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", - "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", - "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", - "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", - "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", - "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", - "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", - "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", - "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", - "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", - "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", - "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", - "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", - "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", - "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", - "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", - "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", - "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", - "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", - "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" + "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", + "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", + "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", + "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", + "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", + "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", + "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", + "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", + "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", + "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", + "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", + "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", + "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", + "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", + "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", + "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", + "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", + "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", + "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", + "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", + "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", + "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", + "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", + "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", + "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", + "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" ], "index": "pypi", - "version": "==37.0.4" + "version": "==38.0.1" }, "dpath": { "hashes": [ @@ -210,19 +214,19 @@ }, "flask": { "hashes": [ - "sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5", - "sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d" + "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", + "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" ], "index": "pypi", - "version": "==2.2.1" + "version": "==2.2.2" }, "flask-jwt-extended": { "hashes": [ - "sha256:188907ea9332bdd123a95a457e7487556770480264ce3b78c8835b4347e324cc", - "sha256:a2571df484c5635ad996d364242ec28fc69f386915cd69b1842639712b84c36d" + "sha256:62b521d75494c290a646ae8acc77123721e4364790f1e64af0038d823961fbf0", + "sha256:a85eebfa17c339a7260c4643475af444784ba6de5588adda67406f0a75599553" ], "index": "pypi", - "version": "==4.4.3" + "version": "==4.4.4" }, "flask-pymongo": { "hashes": [ @@ -288,64 +292,63 @@ }, "greenlet": { "hashes": [ - "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3", - "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711", - "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd", - "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073", - "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708", - "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67", - "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23", - "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1", - "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08", - "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd", - "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2", - "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa", - "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8", - "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40", - "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab", - "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6", - "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc", - "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b", - "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e", - "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963", - "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3", - "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d", - "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d", - "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe", - "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28", - "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3", - "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e", - "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c", - "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d", - "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0", - "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497", - "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee", - "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713", - "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58", - "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a", - "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06", - "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88", - "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965", - "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f", - "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4", - "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5", - "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c", - "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a", - "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1", - "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43", - "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627", - "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b", - "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168", - "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d", - "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5", - "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478", - "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf", - "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce", - "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c", - "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b" + "sha256:0118817c9341ef2b0f75f5af79ac377e4da6ff637e5ee4ac91802c0e379dadb4", + "sha256:048d2bed76c2aa6de7af500ae0ea51dd2267aec0e0f2a436981159053d0bc7cc", + "sha256:07c58e169bbe1e87b8bbf15a5c1b779a7616df9fd3e61cadc9d691740015b4f8", + "sha256:095a980288fe05adf3d002fbb180c99bdcf0f930e220aa66fcd56e7914a38202", + "sha256:0b181e9aa6cb2f5ec0cacc8cee6e5a3093416c841ba32c185c30c160487f0380", + "sha256:1626185d938d7381631e48e6f7713e8d4b964be246073e1a1d15c2f061ac9f08", + "sha256:184416e481295832350a4bf731ba619a92f5689bf5d0fa4341e98b98b1265bd7", + "sha256:1dd51d2650e70c6c4af37f454737bf4a11e568945b27f74b471e8e2a9fd21268", + "sha256:1ec2779774d8e42ed0440cf8bc55540175187e8e934f2be25199bf4ed948cd9e", + "sha256:2cf45e339cabea16c07586306a31cfcc5a3b5e1626d365714d283732afed6809", + "sha256:2fb0aa7f6996879551fd67461d5d3ab0c3c0245da98be90c89fcb7a18d437403", + "sha256:44b4817c34c9272c65550b788913620f1fdc80362b209bc9d7dd2f40d8793080", + "sha256:466ce0928e33421ee84ae04c4ac6f253a3a3e6b8d600a79bd43fd4403e0a7a76", + "sha256:4f166b4aca8d7d489e82d74627a7069ab34211ef5ebb57c300ec4b9337b60fc0", + "sha256:510c3b15587afce9800198b4b142202b323bf4b4b5f9d6c79cb9a35e5e3c30d2", + "sha256:5b756e6730ea59b2745072e28ad27f4c837084688e6a6b3633c8b1e509e6ae0e", + "sha256:5fbe1ab72b998ca77ceabbae63a9b2e2dc2d963f4299b9b278252ddba142d3f1", + "sha256:6200a11f003ec26815f7e3d2ded01b43a3810be3528dd760d2f1fa777490c3cd", + "sha256:65ad1a7a463a2a6f863661329a944a5802c7129f7ad33583dcc11069c17e622c", + "sha256:694ffa7144fa5cc526c8f4512665003a39fa09ef00d19bbca5c8d3406db72fbe", + "sha256:6f5d4b2280ceea76c55c893827961ed0a6eadd5a584a7c4e6e6dd7bc10dfdd96", + "sha256:7532a46505470be30cbf1dbadb20379fb481244f1ca54207d7df3bf0bbab6a20", + "sha256:76a53bfa10b367ee734b95988bd82a9a5f0038a25030f9f23bbbc005010ca600", + "sha256:77e41db75f9958f2083e03e9dd39da12247b3430c92267df3af77c83d8ff9eed", + "sha256:7a43bbfa9b6cfdfaeefbd91038dde65ea2c421dc387ed171613df340650874f2", + "sha256:7b41d19c0cfe5c259fe6c539fd75051cd39a5d33d05482f885faf43f7f5e7d26", + "sha256:7c5227963409551ae4a6938beb70d56bf1918c554a287d3da6853526212fbe0a", + "sha256:870a48007872d12e95a996fca3c03a64290d3ea2e61076aa35d3b253cf34cd32", + "sha256:88b04e12c9b041a1e0bcb886fec709c488192638a9a7a3677513ac6ba81d8e79", + "sha256:8c287ae7ac921dfde88b1c125bd9590b7ec3c900c2d3db5197f1286e144e712b", + "sha256:903fa5716b8fbb21019268b44f73f3748c41d1a30d71b4a49c84b642c2fed5fa", + "sha256:9537e4baf0db67f382eb29255a03154fcd4984638303ff9baaa738b10371fa57", + "sha256:9951dcbd37850da32b2cb6e391f621c1ee456191c6ae5528af4a34afe357c30e", + "sha256:9b2f7d0408ddeb8ea1fd43d3db79a8cefaccadd2a812f021333b338ed6b10aba", + "sha256:9c88e134d51d5e82315a7c32b914a58751b7353eb5268dbd02eabf020b4c4700", + "sha256:9fae214f6c43cd47f7bef98c56919b9222481e833be2915f6857a1e9e8a15318", + "sha256:a3a669f11289a8995d24fbfc0e63f8289dd03c9aaa0cc8f1eab31d18ca61a382", + "sha256:aa741c1a8a8cc25eb3a3a01a62bdb5095a773d8c6a86470bde7f607a447e7905", + "sha256:b0877a9a2129a2c56a2eae2da016743db7d9d6a05d5e1c198f1b7808c602a30e", + "sha256:bcb6c6dd1d6be6d38d6db283747d07fda089ff8c559a835236560a4410340455", + "sha256:caff52cb5cd7626872d9696aee5b794abe172804beb7db52eed1fd5824b63910", + "sha256:cbc1eb55342cbac8f7ec159088d54e2cfdd5ddf61c87b8bbe682d113789331b2", + "sha256:cd16a89efe3a003029c87ff19e9fba635864e064da646bc749fc1908a4af18f3", + "sha256:ce5b64dfe8d0cca407d88b0ee619d80d4215a2612c1af8c98a92180e7109f4b5", + "sha256:d58a5a71c4c37354f9e0c24c9c8321f0185f6945ef027460b809f4bb474bfe41", + "sha256:db41f3845eb579b544c962864cce2c2a0257fe30f0f1e18e51b1e8cbb4e0ac6d", + "sha256:db5b25265010a1b3dca6a174a443a0ed4c4ab12d5e2883a11c97d6e6d59b12f9", + "sha256:dd0404d154084a371e6d2bafc787201612a1359c2dee688ae334f9118aa0bf47", + "sha256:de431765bd5fe62119e0bc6bc6e7b17ac53017ae1782acf88fcf6b7eae475a49", + "sha256:df02fdec0c533301497acb0bc0f27f479a3a63dcdc3a099ae33a902857f07477", + "sha256:e8533f5111704d75de3139bf0b8136d3a6c1642c55c067866fa0a51c2155ee33", + "sha256:f2f908239b7098799b8845e5936c2ccb91d8c2323be02e82f8dcb4a80dcf4a25", + "sha256:f8bfd36f368efe0ab2a6aa3db7f14598aac454b06849fb633b762ddbede1db90", + "sha256:ffe73f9e7aea404722058405ff24041e59d31ca23d1da0895af48050a07b6932" ], "markers": "platform_python_implementation == 'CPython'", - "version": "==1.1.2" + "version": "==1.1.3" }, "idna": { "hashes": [ @@ -451,11 +454,11 @@ }, "marshmallow": { "hashes": [ - "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb", - "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7" + "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408", + "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0" ], "index": "pypi", - "version": "==3.17.0" + "version": "==3.17.1" }, "marshmallow-enum": { "hashes": [ @@ -542,44 +545,45 @@ }, "pydantic": { "hashes": [ - "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44", - "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d", - "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84", - "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555", - "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7", - "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131", - "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8", - "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3", - "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56", - "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0", - "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4", - "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453", - "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044", - "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e", - "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15", - "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb", - "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001", - "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d", - "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3", - "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e", - "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f", - "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c", - "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b", - "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8", - "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567", - "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979", - "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326", - "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb", - "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f", - "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa", - "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747", - "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801", - "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55", - "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08", - "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76" + "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42", + "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624", + "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e", + "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559", + "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709", + "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9", + "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d", + "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52", + "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda", + "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912", + "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c", + "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525", + "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe", + "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41", + "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b", + "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283", + "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965", + "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c", + "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410", + "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5", + "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116", + "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98", + "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f", + "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644", + "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13", + "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd", + "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254", + "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6", + "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488", + "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5", + "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c", + "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1", + "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a", + "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2", + "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d", + "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236" ], "index": "pypi", - "version": "==1.9.2" + "version": "==1.10.2" }, "pyinstaller": { "hashes": [ @@ -600,11 +604,11 @@ }, "pyinstaller-hooks-contrib": { "hashes": [ - "sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56", - "sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001" + "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784", + "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e" ], "markers": "python_version >= '3.7'", - "version": "==2022.8" + "version": "==2022.10" }, "pyjwt": { "hashes": [ @@ -779,10 +783,10 @@ }, "pytz": { "hashes": [ - "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", - "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" + "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197", + "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5" ], - "version": "==2022.1" + "version": "==2022.2.1" }, "pywin32": { "hashes": [ @@ -855,11 +859,11 @@ }, "setuptools": { "hashes": [ - "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", - "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" + "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", + "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57" ], "markers": "python_version >= '3.7'", - "version": "==63.4.1" + "version": "==65.3.0" }, "six": { "hashes": [ @@ -874,24 +878,24 @@ "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" ], - "markers": "python_version >= '3.7'", + "markers": "python_version < '3.8'", "version": "==4.3.0" }, "urllib3": { "hashes": [ - "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc", - "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a" + "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", + "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", - "version": "==1.26.11" + "version": "==1.26.12" }, "werkzeug": { "hashes": [ - "sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a", - "sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a" + "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", + "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" ], "index": "pypi", - "version": "==2.2.1" + "version": "==2.2.2" }, "wirerope": { "hashes": [ @@ -980,13 +984,6 @@ ], "version": "==0.7.12" }, - "atomicwrites": { - "hashes": [ - "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11" - ], - "markers": "sys_platform == 'win32'", - "version": "==1.4.1" - }, "attrs": { "hashes": [ "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", @@ -1000,7 +997,7 @@ "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51", "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.10.3" }, "black": { @@ -1042,11 +1039,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", - "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "version": "==2.1.1" }, "click": { "hashes": [ @@ -1066,57 +1063,66 @@ }, "coverage": { "hashes": [ - "sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32", - "sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7", - "sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996", - "sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55", - "sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46", - "sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de", - "sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039", - "sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee", - "sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1", - "sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f", - "sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63", - "sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083", - "sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe", - "sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0", - "sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6", - "sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe", - "sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933", - "sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0", - "sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c", - "sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07", - "sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8", - "sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b", - "sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e", - "sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120", - "sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f", - "sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e", - "sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd", - "sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f", - "sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386", - "sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8", - "sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae", - "sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc", - "sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783", - "sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d", - "sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c", - "sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97", - "sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978", - "sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf", - "sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29", - "sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39", - "sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452" + "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2", + "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820", + "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827", + "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3", + "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d", + "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145", + "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875", + "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2", + "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74", + "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f", + "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c", + "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973", + "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1", + "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782", + "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0", + "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760", + "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a", + "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3", + "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7", + "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a", + "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f", + "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e", + "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86", + "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa", + "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa", + "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796", + "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a", + "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928", + "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0", + "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac", + "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c", + "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685", + "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d", + "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e", + "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f", + "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558", + "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58", + "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781", + "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a", + "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa", + "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc", + "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892", + "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d", + "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817", + "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1", + "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c", + "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908", + "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19", + "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60", + "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b" ], "index": "pypi", - "version": "==6.4.2" + "version": "==6.4.4" }, "distlib": { "hashes": [ - "sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe", - "sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c" + "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46", + "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" ], - "version": "==0.3.5" + "version": "==0.3.6" }, "dlint": { "hashes": [ @@ -1135,11 +1141,11 @@ }, "filelock": { "hashes": [ - "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404", - "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04" + "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc", + "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4" ], "markers": "python_version >= '3.7'", - "version": "==3.7.1" + "version": "==3.8.0" }, "flake8": { "hashes": [ @@ -1274,10 +1280,11 @@ }, "pathspec": { "hashes": [ - "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", - "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93", + "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d" ], - "version": "==0.9.0" + "markers": "python_version >= '3.7'", + "version": "==0.10.1" }, "platformdirs": { "hashes": [ @@ -1292,7 +1299,7 @@ "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==1.0.0" }, "py": { @@ -1321,11 +1328,11 @@ }, "pygments": { "hashes": [ - "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", - "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" + "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", + "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" ], - "markers": "python_full_version >= '3.6.0'", - "version": "==2.12.0" + "markers": "python_version >= '3.6'", + "version": "==2.13.0" }, "pyparsing": { "hashes": [ @@ -1337,11 +1344,11 @@ }, "pytest": { "hashes": [ - "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", - "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" + "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", + "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" ], "index": "pypi", - "version": "==7.1.2" + "version": "==7.1.3" }, "pytest-cov": { "hashes": [ @@ -1353,10 +1360,10 @@ }, "pytz": { "hashes": [ - "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", - "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" + "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197", + "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5" ], - "version": "==2022.1" + "version": "==2022.2.1" }, "requests": { "hashes": [ @@ -1382,11 +1389,11 @@ }, "setuptools": { "hashes": [ - "sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", - "sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" + "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", + "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57" ], "markers": "python_version >= '3.7'", - "version": "==63.4.1" + "version": "==65.3.0" }, "six": { "hashes": [ @@ -1448,7 +1455,7 @@ "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.0.0" }, "sphinxcontrib-jsmath": { @@ -1493,11 +1500,11 @@ }, "tqdm": { "hashes": [ - "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", - "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" + "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", + "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" ], "index": "pypi", - "version": "==4.64.0" + "version": "==4.64.1" }, "typed-ast": { "hashes": [ @@ -1534,16 +1541,16 @@ "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" ], - "markers": "python_version >= '3.7'", + "markers": "python_version < '3.8'", "version": "==4.3.0" }, "urllib3": { "hashes": [ - "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc", - "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a" + "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", + "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", - "version": "==1.26.11" + "version": "==1.26.12" }, "virtualenv": { "hashes": [ diff --git a/monkey/monkey_island/cc/repository/mongo_credentials_repository.py b/monkey/monkey_island/cc/repository/mongo_credentials_repository.py index 351cac981..237f052e4 100644 --- a/monkey/monkey_island/cc/repository/mongo_credentials_repository.py +++ b/monkey/monkey_island/cc/repository/mongo_credentials_repository.py @@ -59,7 +59,7 @@ class MongoCredentialsRepository(ICredentialsRepository): for encrypted_credentials in list_collection_result: del encrypted_credentials[MONGO_OBJECT_ID_KEY] plaintext_credentials = self._decrypt_credentials_mapping(encrypted_credentials) - collection_result.append(Credentials.from_mapping(plaintext_credentials)) + collection_result.append(Credentials(**plaintext_credentials)) return collection_result except Exception as err: @@ -68,7 +68,7 @@ class MongoCredentialsRepository(ICredentialsRepository): def _save_credentials_to_collection(self, credentials: Sequence[Credentials], collection): try: for c in credentials: - encrypted_credentials = self._encrypt_credentials_mapping(Credentials.to_mapping(c)) + encrypted_credentials = self._encrypt_credentials_mapping(c.dict(simplify=True)) collection.insert_one(encrypted_credentials) except Exception as err: raise StorageError(err) diff --git a/monkey/monkey_island/cc/resources/propagation_credentials.py b/monkey/monkey_island/cc/resources/propagation_credentials.py index 1130d50bd..6700bffd3 100644 --- a/monkey/monkey_island/cc/resources/propagation_credentials.py +++ b/monkey/monkey_island/cc/resources/propagation_credentials.py @@ -29,7 +29,7 @@ class PropagationCredentials(AbstractResource): return propagation_credentials, HTTPStatus.OK def put(self, collection=None): - credentials = [Credentials.from_mapping(c) for c in request.json] + credentials = [Credentials(**c) for c in request.json] if collection == _configured_collection: self._credentials_repository.remove_configured_credentials() self._credentials_repository.save_configured_credentials(credentials) diff --git a/monkey/monkey_island/cc/services/reporting/format_credentials.py b/monkey/monkey_island/cc/services/reporting/format_credentials.py index 721868cdc..60d9b5530 100644 --- a/monkey/monkey_island/cc/services/reporting/format_credentials.py +++ b/monkey/monkey_island/cc/services/reporting/format_credentials.py @@ -1,33 +1,30 @@ import logging from typing import Mapping, Sequence -from common.credentials import CredentialComponentType, Credentials +from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair logger = logging.getLogger(__name__) def format_creds_for_reporting(credentials: Sequence[Credentials]) -> Sequence[Mapping]: - logger.info("Stolen creds generated for reporting") - formatted_creds = [] cred_type_dict = { - CredentialComponentType.PASSWORD: "Clear Password", - CredentialComponentType.LM_HASH: "LM hash", - CredentialComponentType.NT_HASH: "NTLM hash", - CredentialComponentType.SSH_KEYPAIR: "Clear SSH private key", + Password: "Clear Password", + LMHash: "LM hash", + NTHash: "NTLM hash", + SSHKeypair: "Clear SSH private key", } for cred in credentials: secret = cred.secret if secret is None: continue - if secret.credential_type not in cred_type_dict: + if type(secret) not in cred_type_dict: continue username = _get_username(cred) cred_row = { "username": username, - "_type": secret.credential_type.name, - "type": cred_type_dict[secret.credential_type], + "type": cred_type_dict[type(secret)], } if cred_row not in formatted_creds: formatted_creds.append(cred_row) diff --git a/monkey/monkey_island/cc/services/representations.py b/monkey/monkey_island/cc/services/representations.py index e679231c1..19218ba37 100644 --- a/monkey/monkey_island/cc/services/representations.py +++ b/monkey/monkey_island/cc/services/representations.py @@ -5,6 +5,7 @@ from typing import Any import bson from flask import make_response +from pydantic import BaseModel from common.utils import IJSONSerializable @@ -23,6 +24,8 @@ class APIEncoder(JSONEncoder): return loads(value.__class__.to_json(value)) if issubclass(type(value), set): return list(value) + if issubclass(type(value), BaseModel): + return value.dict(simplify=True) try: return JSONEncoder.default(self, value) except TypeError: diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py index 4d675b42c..f11d76002 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py @@ -19,8 +19,6 @@ class CredentialsParser: self._parse_credentials(telemetry_dict, _agent_configuration) def _parse_credentials(self, telemetry_dict: Mapping, _agent_configuration): - credentials = [ - Credentials.from_mapping(credential) for credential in telemetry_dict["data"] - ] + credentials = [Credentials(**credential) for credential in telemetry_dict["data"]] self._credentials_repository.save_stolen_credentials(credentials) diff --git a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js index a26987b37..3b8546614 100644 --- a/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js +++ b/monkey/monkey_island/cc/ui/src/components/configuration-components/ReformatHook.js @@ -1,20 +1,26 @@ import {defaultCredentials} from '../../services/configuration/propagation/credentials'; +import {PlaintextTypes, SecretTypes} from '../utils/CredentialTitles.js'; import _ from 'lodash'; export function reformatConfig(config, reverse = false) { let formattedConfig = _.clone(config); if (reverse) { - if(formattedConfig['payloads'].length === 1){ + if (formattedConfig['payloads'].length === 1) { // Second click on Export - formattedConfig['payloads'] = [{'name': 'ransomware', 'options': formattedConfig['payloads'][0]['options']}]; + formattedConfig['payloads'] = [{ + 'name': 'ransomware', + 'options': formattedConfig['payloads'][0]['options'] + }]; } else { - formattedConfig['payloads'] = [{'name': 'ransomware', 'options': formattedConfig['payloads']}]; + formattedConfig['payloads'] = [{ + 'name': 'ransomware', + 'options': formattedConfig['payloads'] + }]; } formattedConfig['keep_tunnel_open_time'] = formattedConfig['advanced']['keep_tunnel_open_time']; } else { - if(formattedConfig['payloads'].length !== 0) - { + if (formattedConfig['payloads'].length !== 0) { formattedConfig['payloads'] = formattedConfig['payloads'][0]['options']; } else { formattedConfig['payloads'] = {'encryption': {}, 'other_behaviors': {}} @@ -29,23 +35,26 @@ export function formatCredentialsForForm(credentials) { let formattedCredentials = _.clone(defaultCredentials); for (let i = 0; i < credentials.length; i++) { let identity = credentials[i]['identity']; - if(identity !== null) { + if (identity !== null) { formattedCredentials['exploit_user_list'].push(identity.username) } let secret = credentials[i]['secret']; - if(secret !== null){ - if (secret['credential_type'] === 'PASSWORD') { - formattedCredentials['exploit_password_list'].push(secret['password']) + if (secret !== null) { + if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.Password)) { + formattedCredentials['exploit_password_list'].push(secret[SecretTypes.Password]) } - if (secret['credential_type'] === 'NT_HASH') { - formattedCredentials['exploit_ntlm_hash_list'].push(secret['nt_hash']) + if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.NTHash)) { + formattedCredentials['exploit_ntlm_hash_list'].push(secret[SecretTypes.NTHash]) } - if (secret['credential_type'] === 'LM_HASH') { - formattedCredentials['exploit_lm_hash_list'].push(secret['lm_hash']) + if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.LMHash)) { + formattedCredentials['exploit_lm_hash_list'].push(secret[SecretTypes.LMHash]) } - if (secret['credential_type'] === 'SSH_KEY') { - let keypair = {'public_key': secret['public_key'], 'private_key': secret['private_key']} + if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.PrivateKey)) { + let keypair = { + 'public_key': secret[PlaintextTypes.PublicKey], + 'private_key': secret[SecretTypes.PrivateKey] + } formattedCredentials['exploit_ssh_keys'].push(keypair) } } @@ -64,43 +73,36 @@ export function formatCredentialsForIsland(credentials) { let usernames = credentials['exploit_user_list']; for (let i = 0; i < usernames.length; i++) { formattedCredentials.push({ - 'identity': {'username': usernames[i], 'credential_type': 'USERNAME'}, + 'identity': {'username': usernames[i]}, 'secret': null }) } - let passwords = credentials['exploit_password_list']; - for (let i = 0; i < passwords.length; i++) { - formattedCredentials.push({ - 'identity': null, - 'secret': {'credential_type': 'PASSWORD', 'password': passwords[i]} - }) - } - - let nt_hashes = credentials['exploit_ntlm_hash_list']; - for (let i = 0; i < nt_hashes.length; i++) { - formattedCredentials.push({ - 'identity': null, - 'secret': {'credential_type': 'NT_HASH', 'nt_hash': nt_hashes[i]} - }) - } - - let lm_hashes = credentials['exploit_lm_hash_list']; - for (let i = 0; i < lm_hashes.length; i++) { - formattedCredentials.push({ - 'identity': null, - 'secret': {'credential_type': 'LM_HASH', 'lm_hash': lm_hashes[i]} - }) - } + formattedCredentials.push(...getFormattedCredentials(credentials['exploit_password_list'], SecretTypes.Password)) + formattedCredentials.push(...getFormattedCredentials(credentials['exploit_ntlm_hash_list'], SecretTypes.NTHash)) + formattedCredentials.push(...getFormattedCredentials(credentials['exploit_lm_hash_list'], SecretTypes.LMHash)) let ssh_keys = credentials['exploit_ssh_keys']; for (let i = 0; i < ssh_keys.length; i++) { formattedCredentials.push({ 'identity': null, - 'secret': {'credential_type': 'SSH_KEYPAIR', 'private_key': ssh_keys[i]['private_key'], - 'public_key': ssh_keys[i]['public_key']} + 'secret': { + 'private_key': ssh_keys[i]['private_key'], + 'public_key': ssh_keys[i]['public_key'] + } }) } return formattedCredentials; } + +function getFormattedCredentials(credentials, keyOfSecret) { + let formattedCredentials = []; + for (let i = 0; i < credentials.length; i++) { + formattedCredentials.push({ + 'identity': null, + 'secret': {[keyOfSecret]: credentials[i]} + }) + } + return formattedCredentials; +} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js index c1eb57198..fc77f6d4f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/credentialParsing.js @@ -1,66 +1,70 @@ -export function getAllUsernames(stolen, configured){ - let usernames = []; - usernames.push(...getCredentialsUsernames(stolen)); - usernames.push(...getCredentialsUsernames(configured)); - return usernames; +import {CredentialTitles, SecretTypes} from '../utils/CredentialTitles.js'; + +export function getAllUsernames(stolen, configured) { + let usernames = new Set(); + usernames.add(...getCredentialsUsernames(stolen)); + usernames.add(...getCredentialsUsernames(configured)); + return Array.from(usernames); } export function getCredentialsUsernames(credentials) { let usernames = []; - for(let i = 0; i < credentials.length; i++){ + for (let i = 0; i < credentials.length; i++) { let username = credentials[i]['identity']; - if(username !== null) { + if (username !== null) { usernames.push(username['username']); } } return usernames; } -export function getAllSecrets(stolen, configured){ - let secrets = []; - for(let i = 0; i < stolen.length; i++){ +export function getAllSecrets(stolen, configured) { + let secrets = new Set(); + for (let i = 0; i < stolen.length; i++) { let secret = stolen[i]['secret']; - if(secret !== null){ - secrets.push(getSecretsFromCredential(secret)); + if (secret !== null) { + secrets.add(reformatSecret(secret)); } } - for(let i = 0; i < configured.length; i++){ + for (let i = 0; i < configured.length; i++) { let secret = configured[i]['secret']; - if(secret !== null){ - secrets.push(getSecretsFromCredential(secret)); + if (secret !== null) { + secrets.add(reformatSecret(secret)); } } - return secrets; + return Array.from(secrets); } -function getSecretsFromCredential(credential) { - if(credential['credential_type'] === 'SSH_KEYPAIR'){ - return {'type': 'SSH keypair', 'content': credential['private_key']} +function reformatSecret(secret) { + if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.Password)) { + return {'title': CredentialTitles.Password, 'content': secret[SecretTypes.Password]} } - if(credential['credential_type'] === 'NT_HASH'){ - return {'type': 'NT hash', 'content': credential['nt_hash']} + if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.NTHash)) { + return {'title': CredentialTitles.NTHash, 'content': secret[SecretTypes.NTHash]} } - if(credential['credential_type'] === 'LM_HASH'){ - return {'type': 'LM hash', 'content': credential['lm_hash']} + if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.LMHash)) { + return {'title': CredentialTitles.LMHash, 'content': secret[SecretTypes.LMHash]} } - if(credential['credential_type'] === 'PASSWORD'){ - return {'type': 'Password', 'content': credential['password']} + if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.PrivateKey)) { + return { + 'title': CredentialTitles.SSHKeys, + 'content': secret[SecretTypes.PrivateKey] + } } } export function getCredentialsTableData(credentials) { + let table_data = []; - let table_data = []; + let identites = getCredentialsUsernames(credentials); + let secrets = getAllSecrets(credentials, []) - let identites = getCredentialsUsernames(credentials); - let secrets = getAllSecrets(credentials, []) + for (let i = 0; i < credentials.length; i++) { + let row_data = {}; + row_data['username'] = identites[i]; + row_data['title'] = secrets[i]['title']; + table_data.push(row_data); + } - for(let i=0; i : diff --git a/monkey/monkey_island/cc/ui/src/components/utils/CredentialTitles.js b/monkey/monkey_island/cc/ui/src/components/utils/CredentialTitles.js new file mode 100644 index 000000000..d24c1e3e1 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/utils/CredentialTitles.js @@ -0,0 +1,18 @@ +export const CredentialTitles = { + Password: 'Clear Password', + SSHKeys: 'Clear SSH private key', + LMHash: 'LM hash', + NTHash: 'NT hash', + Username: 'Username' +} + +export const SecretTypes = { + Password: 'password', + PrivateKey: 'private_key', + LMHash: 'lm_hash', + NTHash: 'nt_hash' +} + +export const PlaintextTypes = { + PublicKey: 'public_key' +} diff --git a/monkey/tests/data_for_tests/propagation_credentials.py b/monkey/tests/data_for_tests/propagation_credentials.py index 6efe9b7af..35521e7b6 100644 --- a/monkey/tests/data_for_tests/propagation_credentials.py +++ b/monkey/tests/data_for_tests/propagation_credentials.py @@ -1,31 +1,64 @@ +from itertools import product + +from pydantic import SecretStr + from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username USERNAME = "m0nk3y_user" SPECIAL_USERNAME = "m0nk3y.user" -NT_HASH = "C1C58F96CDF212B50837BC11A00BE47C" -LM_HASH = "299BD128C1101FD6299BD128C1101FD6" -PASSWORD_1 = "trytostealthis" -PASSWORD_2 = "password!" -PASSWORD_3 = "rubberbabybuggybumpers" +PLAINTEXT_NT_HASH = "C1C58F96CDF212B50837BC11A00BE47C" +PLAINTEXT_LM_HASH = "299BD128C1101FD6299BD128C1101FD6" +PLAINTEXT_PASSWORD = "trytostealthis" +PLAINTEXT_PRIVATE_KEY = "MY_PRIVATE_KEY" +NT_HASH = SecretStr(PLAINTEXT_NT_HASH) +LM_HASH = SecretStr(PLAINTEXT_LM_HASH) +PASSWORD_1 = SecretStr(PLAINTEXT_PASSWORD) +PASSWORD_2 = SecretStr("password!") +PASSWORD_3 = SecretStr("rubberbabybuggybumpers") PUBLIC_KEY = "MY_PUBLIC_KEY" -PRIVATE_KEY = "MY_PRIVATE_KEY" +PRIVATE_KEY = SecretStr(PLAINTEXT_PRIVATE_KEY) -PASSWORD_CREDENTIALS_1 = Credentials(identity=Username(USERNAME), secret=Password(PASSWORD_1)) -PASSWORD_CREDENTIALS_2 = Credentials(identity=Username(USERNAME), secret=Password(PASSWORD_2)) -LM_HASH_CREDENTIALS = Credentials(identity=Username(SPECIAL_USERNAME), secret=LMHash(LM_HASH)) -NT_HASH_CREDENTIALS = Credentials(identity=Username(USERNAME), secret=NTHash(NT_HASH)) -SSH_KEY_CREDENTIALS = Credentials( - identity=Username(USERNAME), secret=SSHKeypair(PRIVATE_KEY, PUBLIC_KEY) +IDENTITIES = [Username(username=USERNAME), None, Username(username=SPECIAL_USERNAME)] +IDENTITY_DICTS = [{"username": USERNAME}, None] + +SECRETS = ( + Password(password=PASSWORD_1), + Password(password=PASSWORD_2), + Password(password=PASSWORD_3), + LMHash(lm_hash=LM_HASH), + NTHash(nt_hash=NT_HASH), + SSHKeypair(private_key=PRIVATE_KEY, public_key=PUBLIC_KEY), + None, ) -EMPTY_SECRET_CREDENTIALS = Credentials(identity=Username(USERNAME), secret=None) -EMPTY_IDENTITY_CREDENTIALS = Credentials(identity=None, secret=Password(PASSWORD_3)) - -PROPAGATION_CREDENTIALS = [ - PASSWORD_CREDENTIALS_1, - LM_HASH_CREDENTIALS, - NT_HASH_CREDENTIALS, - PASSWORD_CREDENTIALS_2, - SSH_KEY_CREDENTIALS, - EMPTY_SECRET_CREDENTIALS, - EMPTY_IDENTITY_CREDENTIALS, +SECRET_DICTS = [ + {"password": PASSWORD_1}, + {"lm_hash": LM_HASH}, + {"nt_hash": NT_HASH}, + { + "public_key": PUBLIC_KEY, + "private_key": PRIVATE_KEY, + }, + None, +] + +CREDENTIALS = [ + Credentials(identity=identity, secret=secret) + for identity, secret in product(IDENTITIES, SECRETS) +] + +FULL_CREDENTIALS = [ + credentials + for credentials in CREDENTIALS + if not (credentials.identity is None and credentials.secret is None) +] + +CREDENTIALS_DICTS = [ + {"identity": identity, "secret": secret} + for identity, secret in product(IDENTITY_DICTS, SECRET_DICTS) +] + +FULL_CREDENTIALS_DICTS = [ + credentials + for credentials in CREDENTIALS_DICTS + if not (credentials["identity"] is None and credentials["secret"] is None) ] diff --git a/monkey/tests/unit_tests/common/credentials/conftest.py b/monkey/tests/unit_tests/common/credentials/conftest.py new file mode 100644 index 000000000..ad293354e --- /dev/null +++ b/monkey/tests/unit_tests/common/credentials/conftest.py @@ -0,0 +1,20 @@ +from typing import List, Union + +import pytest + + +@pytest.fixture(scope="session") +def valid_ntlm_hash() -> str: + return "E520AC67419A9A224A3B108F3FA6CB6D" + + +@pytest.fixture(scope="session") +def invalid_ntlm_hashes() -> List[Union[str, int, float]]: + return [ + 0, + 1, + 2.0, + "invalid", + "0123456789012345678901234568901", + "E52GAC67419A9A224A3B108F3FA6CB6D", + ] diff --git a/monkey/tests/unit_tests/common/credentials/test_credential_components.py b/monkey/tests/unit_tests/common/credentials/test_credential_components.py deleted file mode 100644 index 55a2b9279..000000000 --- a/monkey/tests/unit_tests/common/credentials/test_credential_components.py +++ /dev/null @@ -1,148 +0,0 @@ -from typing import Any, Mapping - -import pytest -from marshmallow.exceptions import ValidationError - -from common.credentials import ( - CredentialComponentType, - LMHash, - NTHash, - Password, - SSHKeypair, - Username, -) -from common.credentials.lm_hash import LMHashSchema -from common.credentials.nt_hash import NTHashSchema -from common.credentials.password import PasswordSchema -from common.credentials.ssh_keypair import SSHKeypairSchema -from common.credentials.username import UsernameSchema - -PARAMETRIZED_PARAMETER_NAMES = ( - "credential_component_class, schema_class, credential_component_type, credential_component_data" -) - -PARAMETRIZED_PARAMETER_VALUES = [ - (Username, UsernameSchema, CredentialComponentType.USERNAME, {"username": "test_user"}), - (Password, PasswordSchema, CredentialComponentType.PASSWORD, {"password": "123456"}), - ( - LMHash, - LMHashSchema, - CredentialComponentType.LM_HASH, - {"lm_hash": "E52CAC67419A9A224A3B108F3FA6CB6D"}, - ), - ( - NTHash, - NTHashSchema, - CredentialComponentType.NT_HASH, - {"nt_hash": "E52CAC67419A9A224A3B108F3FA6CB6D"}, - ), - ( - SSHKeypair, - SSHKeypairSchema, - CredentialComponentType.SSH_KEYPAIR, - {"public_key": "TEST_PUBLIC_KEY", "private_key": "TEST_PRIVATE_KEY"}, - ), -] - - -INVALID_COMPONENT_DATA = { - CredentialComponentType.USERNAME: ({"username": None}, {"username": 1}, {"username": 2.0}), - CredentialComponentType.PASSWORD: ({"password": None}, {"password": 1}, {"password": 2.0}), - CredentialComponentType.LM_HASH: ( - {"lm_hash": None}, - {"lm_hash": 1}, - {"lm_hash": 2.0}, - {"lm_hash": "0123456789012345678901234568901"}, - {"lm_hash": "E52GAC67419A9A224A3B108F3FA6CB6D"}, - ), - CredentialComponentType.NT_HASH: ( - {"nt_hash": None}, - {"nt_hash": 1}, - {"nt_hash": 2.0}, - {"nt_hash": "0123456789012345678901234568901"}, - {"nt_hash": "E52GAC67419A9A224A3B108F3FA6CB6D"}, - ), - CredentialComponentType.SSH_KEYPAIR: ( - {"public_key": None, "private_key": "TEST_PRIVATE_KEY"}, - {"public_key": "TEST_PUBLIC_KEY", "private_key": None}, - {"public_key": 1, "private_key": "TEST_PRIVATE_KEY"}, - {"public_key": "TEST_PUBLIC_KEY", "private_key": 999}, - ), -} - - -def build_component_dict( - credential_component_type: CredentialComponentType, credential_component_data: Mapping[str, Any] -): - return {"credential_type": credential_component_type.name, **credential_component_data} - - -@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES) -def test_credential_component_serialize( - credential_component_class, schema_class, credential_component_type, credential_component_data -): - schema = schema_class() - constructed_object = credential_component_class(**credential_component_data) - - serialized_object = schema.dump(constructed_object) - - assert serialized_object == build_component_dict( - credential_component_type, credential_component_data - ) - - -@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES) -def test_credential_component_deserialize( - credential_component_class, schema_class, credential_component_type, credential_component_data -): - schema = schema_class() - credential_dict = build_component_dict(credential_component_type, credential_component_data) - expected_deserialized_object = credential_component_class(**credential_component_data) - - deserialized_object = credential_component_class(**schema.load(credential_dict)) - - assert deserialized_object == expected_deserialized_object - - -@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES) -def test_invalid_credential_type( - credential_component_class, schema_class, credential_component_type, credential_component_data -): - invalid_component_dict = build_component_dict( - credential_component_type, credential_component_data - ) - invalid_component_dict["credential_type"] = "INVALID" - schema = schema_class() - - with pytest.raises(ValidationError): - credential_component_class(**schema.load(invalid_component_dict)) - - -@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES) -def test_encorrect_credential_type( - credential_component_class, schema_class, credential_component_type, credential_component_data -): - incorrect_component_dict = build_component_dict( - credential_component_type, credential_component_data - ) - incorrect_component_dict["credential_type"] = ( - CredentialComponentType.USERNAME.name - if credential_component_type != CredentialComponentType.USERNAME - else CredentialComponentType.PASSWORD - ) - schema = schema_class() - - with pytest.raises(ValidationError): - credential_component_class(**schema.load(incorrect_component_dict)) - - -@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES) -def test_invalid_values( - credential_component_class, schema_class, credential_component_type, credential_component_data -): - schema = schema_class() - - for invalid_component_data in INVALID_COMPONENT_DATA[credential_component_type]: - component_dict = build_component_dict(credential_component_type, invalid_component_data) - with pytest.raises(ValidationError): - credential_component_class(**schema.load(component_dict)) diff --git a/monkey/tests/unit_tests/common/credentials/test_credentials.py b/monkey/tests/unit_tests/common/credentials/test_credentials.py index 4eeb8647f..09d68a4c3 100644 --- a/monkey/tests/unit_tests/common/credentials/test_credentials.py +++ b/monkey/tests/unit_tests/common/credentials/test_credentials.py @@ -1,122 +1,72 @@ -import json -from itertools import product +import logging +from pathlib import Path import pytest +from pydantic import SecretBytes +from pydantic.types import SecretStr from tests.data_for_tests.propagation_credentials import ( + CREDENTIALS, + CREDENTIALS_DICTS, LM_HASH, - NT_HASH, PASSWORD_1, + PLAINTEXT_LM_HASH, + PLAINTEXT_PASSWORD, + PLAINTEXT_PRIVATE_KEY, PRIVATE_KEY, - PUBLIC_KEY, - USERNAME, ) -from common.credentials import ( - Credentials, - InvalidCredentialComponentError, - InvalidCredentialsError, - LMHash, - NTHash, - Password, - SSHKeypair, - Username, -) - -IDENTITIES = [Username(USERNAME), None] -IDENTITY_DICTS = [{"credential_type": "USERNAME", "username": USERNAME}, None] - -SECRETS = ( - Password(PASSWORD_1), - LMHash(LM_HASH), - NTHash(NT_HASH), - SSHKeypair(PRIVATE_KEY, PUBLIC_KEY), - None, -) -SECRET_DICTS = [ - {"credential_type": "PASSWORD", "password": PASSWORD_1}, - {"credential_type": "LM_HASH", "lm_hash": LM_HASH}, - {"credential_type": "NT_HASH", "nt_hash": NT_HASH}, - { - "credential_type": "SSH_KEYPAIR", - "public_key": PUBLIC_KEY, - "private_key": PRIVATE_KEY, - }, - None, -] - -CREDENTIALS = [ - Credentials(identity, secret) - for identity, secret in product(IDENTITIES, SECRETS) - if not (identity is None and secret is None) -] - -CREDENTIALS_DICTS = [ - {"identity": identity, "secret": secret} - for identity, secret in product(IDENTITY_DICTS, SECRET_DICTS) - if not (identity is None and secret is None) -] +from common.base_models import InfectionMonkeyBaseModel +from common.credentials import Credentials +from common.credentials.credentials import get_plaintext @pytest.mark.parametrize( "credentials, expected_credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS) ) def test_credentials_serialization_json(credentials, expected_credentials_dict): - serialized_credentials = Credentials.to_json(credentials) + serialized_credentials = credentials.json() + deserialized_credentials = Credentials.parse_raw(serialized_credentials) - assert json.loads(serialized_credentials) == expected_credentials_dict + assert credentials == deserialized_credentials -@pytest.mark.parametrize( - "credentials, expected_credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS) -) -def test_credentials_serialization_mapping(credentials, expected_credentials_dict): - serialized_credentials = Credentials.to_mapping(credentials) - - assert serialized_credentials == expected_credentials_dict +logger = logging.getLogger() +logger.level = logging.DEBUG -@pytest.mark.parametrize( - "expected_credentials, credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS) -) -def test_credentials_deserialization__from_mapping(expected_credentials, credentials_dict): - deserialized_credentials = Credentials.from_mapping(credentials_dict) +def test_credentials_secrets_not_logged(caplog): + class TestSecret(InfectionMonkeyBaseModel): + some_secret: SecretStr + some_secret_in_bytes: SecretBytes - assert deserialized_credentials == expected_credentials + class TestCredentials(Credentials): + secret: TestSecret + + sensitive = "super_secret" + creds = TestCredentials( + identity=None, + secret=TestSecret(some_secret=sensitive, some_secret_in_bytes=sensitive.encode()), + ) + + logging.getLogger().info( + f"{creds.secret.some_secret} and" f" {creds.secret.some_secret_in_bytes}" + ) + + assert sensitive not in caplog.text -@pytest.mark.parametrize( - "expected_credentials, credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS) -) -def test_credentials_deserialization__from_json(expected_credentials, credentials_dict): - deserialized_credentials = Credentials.from_json(json.dumps(credentials_dict)) - - assert deserialized_credentials == expected_credentials +_plaintext = [ + PLAINTEXT_PASSWORD, + PLAINTEXT_PRIVATE_KEY, + PLAINTEXT_LM_HASH, + "", + "already_plaintext", + Path("C:\\jolly_fella"), + None, +] +_hidden = [PASSWORD_1, PRIVATE_KEY, LM_HASH, "", "already_plaintext", Path("C:\\jolly_fella"), None] -def test_credentials_deserialization__invalid_credentials(): - invalid_data = {"secret": SECRET_DICTS[0], "unknown_key": []} - with pytest.raises(InvalidCredentialsError): - Credentials.from_mapping(invalid_data) - - -def test_credentials_deserialization__invalid_component_type(): - invalid_data = { - "secret": SECRET_DICTS[0], - "identity": {"credential_type": "FAKE", "username": "user1"}, - } - with pytest.raises(InvalidCredentialsError): - Credentials.from_mapping(invalid_data) - - -def test_credentials_deserialization__invalid_component(): - invalid_data = { - "secret": SECRET_DICTS[0], - "identity": {"credential_type": "USERNAME", "unknown_field": "user1"}, - } - with pytest.raises(InvalidCredentialComponentError): - Credentials.from_mapping(invalid_data) - - -def test_create_credentials__none_none(): - with pytest.raises(InvalidCredentialsError): - Credentials(None, None) +@pytest.mark.parametrize("expected, hidden", list(zip(_plaintext, _hidden))) +def test_get_plain_text(expected, hidden): + assert expected == get_plaintext(hidden) diff --git a/monkey/tests/unit_tests/common/credentials/test_lm_hash.py b/monkey/tests/unit_tests/common/credentials/test_lm_hash.py new file mode 100644 index 000000000..4bcfb3270 --- /dev/null +++ b/monkey/tests/unit_tests/common/credentials/test_lm_hash.py @@ -0,0 +1,14 @@ +import pytest + +from common.credentials import LMHash + + +def test_construct_valid_nt_hash(valid_ntlm_hash): + # This test will fail if an exception is raised + LMHash(lm_hash=valid_ntlm_hash) + + +def test_construct_invalid_nt_hash(invalid_ntlm_hashes): + for invalid_hash in invalid_ntlm_hashes: + with pytest.raises(ValueError): + LMHash(lm_hash=invalid_hash) diff --git a/monkey/tests/unit_tests/common/credentials/test_nt_hash.py b/monkey/tests/unit_tests/common/credentials/test_nt_hash.py new file mode 100644 index 000000000..1c60f4fca --- /dev/null +++ b/monkey/tests/unit_tests/common/credentials/test_nt_hash.py @@ -0,0 +1,14 @@ +import pytest + +from common.credentials import NTHash + + +def test_construct_valid_nt_hash(valid_ntlm_hash): + # This test will fail if an exception is raised + NTHash(nt_hash=valid_ntlm_hash) + + +def test_construct_invalid_nt_hash(invalid_ntlm_hashes): + for invalid_hash in invalid_ntlm_hashes: + with pytest.raises(ValueError): + NTHash(nt_hash=invalid_hash) diff --git a/monkey/tests/unit_tests/common/credentials/test_ntlm_hash.py b/monkey/tests/unit_tests/common/credentials/test_ntlm_hash.py deleted file mode 100644 index 5f50110e8..000000000 --- a/monkey/tests/unit_tests/common/credentials/test_ntlm_hash.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest - -from common.credentials import InvalidCredentialComponentError, LMHash, NTHash - -VALID_HASH = "E520AC67419A9A224A3B108F3FA6CB6D" -INVALID_HASHES = ( - 0, - 1, - 2.0, - "invalid", - "0123456789012345678901234568901", - "E52GAC67419A9A224A3B108F3FA6CB6D", -) - - -@pytest.mark.parametrize("ntlm_hash_class", (LMHash, NTHash)) -def test_construct_valid_ntlm_hash(ntlm_hash_class): - # This test will fail if an exception is raised - ntlm_hash_class(VALID_HASH) - - -@pytest.mark.parametrize("ntlm_hash_class", (LMHash, NTHash)) -def test_construct_invalid_ntlm_hash(ntlm_hash_class): - for invalid_hash in INVALID_HASHES: - with pytest.raises(InvalidCredentialComponentError): - ntlm_hash_class(invalid_hash) diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py index 0476d8a13..11c6d695b 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py @@ -41,9 +41,9 @@ def test_pypykatz_result_parsing(monkeypatch): win_creds = [WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash="")] patch_pypykatz(win_creds, monkeypatch) - username = Username("user") - password = Password("secret") - expected_credentials = Credentials(username, password) + username = Username(username="user") + password = Password(password="secret") + expected_credentials = Credentials(identity=username, secret=password) collected_credentials = collect_credentials() assert len(collected_credentials) == 1 @@ -70,10 +70,13 @@ def test_pypykatz_result_parsing_defaults(monkeypatch): patch_pypykatz(win_creds, monkeypatch) # Expected credentials - username = Username("user2") - password = Password("secret2") - lm_hash = LMHash("0182BD0BD4444BF8FC83B5D9042EED2E") - expected_credentials = [Credentials(username, password), Credentials(username, lm_hash)] + username = Username(username="user2") + password = Password(password="secret2") + lm_hash = LMHash(lm_hash="0182BD0BD4444BF8FC83B5D9042EED2E") + expected_credentials = [ + Credentials(identity=username, secret=password), + Credentials(identity=username, secret=lm_hash), + ] collected_credentials = collect_credentials() assert len(collected_credentials) == 2 @@ -91,9 +94,12 @@ def test_pypykatz_result_parsing_no_identities(monkeypatch): ] patch_pypykatz(win_creds, monkeypatch) - lm_hash = LMHash("0182BD0BD4444BF8FC83B5D9042EED2E") - nt_hash = NTHash("E9F85516721DDC218359AD5280DB4450") - expected_credentials = [Credentials(None, lm_hash), Credentials(None, nt_hash)] + lm_hash = LMHash(lm_hash="0182BD0BD4444BF8FC83B5D9042EED2E") + nt_hash = NTHash(nt_hash="E9F85516721DDC218359AD5280DB4450") + expected_credentials = [ + Credentials(identity=None, secret=lm_hash), + Credentials(identity=None, secret=nt_hash), + ] collected_credentials = collect_credentials() assert len(collected_credentials) == 2 @@ -112,7 +118,7 @@ def test_pypykatz_result_parsing_no_secrets(monkeypatch): ] patch_pypykatz(win_creds, monkeypatch) - expected_credentials = [Credentials(Username(username), None)] + expected_credentials = [Credentials(identity=Username(username=username), secret=None)] collected_credentials = collect_credentials() assert len(collected_credentials) == 1 diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py index 1c7fbe1b4..bf41060d8 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_ssh_credentials_collector.py @@ -31,7 +31,6 @@ def test_ssh_credentials_empty_results(monkeypatch, ssh_creds, patch_telemetry_m def test_ssh_info_result_parsing(monkeypatch, patch_telemetry_messenger): - ssh_creds = [ { "name": "ubuntu", @@ -56,13 +55,15 @@ def test_ssh_info_result_parsing(monkeypatch, patch_telemetry_messenger): patch_ssh_handler(ssh_creds, monkeypatch) # Expected credentials - username = Username("ubuntu") - username2 = Username("mcus") - username3 = Username("guest") + username = Username(username="ubuntu") + username2 = Username(username="mcus") + username3 = Username(username="guest") - ssh_keypair1 = SSHKeypair("ExtremelyGoodPrivateKey", "SomePublicKeyUbuntu") - ssh_keypair2 = SSHKeypair("", "AnotherPublicKey") - ssh_keypair3 = SSHKeypair("PrivKey", "PubKey") + ssh_keypair1 = SSHKeypair( + private_key="ExtremelyGoodPrivateKey", public_key="SomePublicKeyUbuntu" + ) + ssh_keypair2 = SSHKeypair(private_key="", public_key="AnotherPublicKey") + ssh_keypair3 = SSHKeypair(private_key="PrivKey", public_key="PubKey") expected = [ Credentials(identity=username, secret=ssh_keypair1), diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_add_credentials_from_event.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_add_credentials_from_event.py index e01321fce..26f4f6a50 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_add_credentials_from_event.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_add_credentials_from_event.py @@ -8,7 +8,11 @@ from infection_monkey.credential_repository import ( add_credentials_from_event_to_propagation_credentials_repository, ) -credentials = [Credentials(identity=Username("test_username"), secret=Password("some_password"))] +credentials = [ + Credentials( + identity=Username(username="test_username"), secret=Password(password="some_password") + ) +] credentials_stolen_event = CredentialsStolenEvent( source=UUID("f811ad00-5a68-4437-bd51-7b5cc1768ad5"), diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py index c992aaf85..c6ae98f11 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_propagation_credentials_repository.py @@ -1,14 +1,15 @@ from unittest.mock import MagicMock import pytest +from pydantic import SecretStr from tests.data_for_tests.propagation_credentials import ( + CREDENTIALS, LM_HASH, NT_HASH, PASSWORD_1, PASSWORD_2, PASSWORD_3, PRIVATE_KEY, - PROPAGATION_CREDENTIALS, PUBLIC_KEY, SPECIAL_USERNAME, USERNAME, @@ -17,7 +18,6 @@ from tests.data_for_tests.propagation_credentials import ( from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username from infection_monkey.credential_repository import AggregatingPropagationCredentialsRepository -CONTROL_CHANNEL_CREDENTIALS = PROPAGATION_CREDENTIALS TRANSFORMED_CONTROL_CHANNEL_CREDENTIALS = { "exploit_user_list": {USERNAME, SPECIAL_USERNAME}, "exploit_password_list": {PASSWORD_1, PASSWORD_2, PASSWORD_3}, @@ -31,37 +31,43 @@ EMPTY_CHANNEL_CREDENTIALS = [] STOLEN_USERNAME_1 = "user1" STOLEN_USERNAME_2 = "user2" STOLEN_USERNAME_3 = "user3" -STOLEN_PASSWORD_1 = "abcdefg" -STOLEN_PASSWORD_2 = "super_secret" +STOLEN_PASSWORD_1 = SecretStr("abcdefg") +STOLEN_PASSWORD_2 = SecretStr("super_secret") STOLEN_PUBLIC_KEY_1 = "some_public_key_1" STOLEN_PUBLIC_KEY_2 = "some_public_key_2" -STOLEN_LM_HASH = "AAD3B435B51404EEAAD3B435B51404EE" -STOLEN_NT_HASH = "C0172DFF622FE29B5327CB79DC12D24C" -STOLEN_PRIVATE_KEY_1 = "some_private_key_1" -STOLEN_PRIVATE_KEY_2 = "some_private_key_2" +STOLEN_LM_HASH = SecretStr("AAD3B435B51404EEAAD3B435B51404EE") +STOLEN_NT_HASH = SecretStr("C0172DFF622FE29B5327CB79DC12D24C") +STOLEN_PRIVATE_KEY_1 = SecretStr("some_private_key_1") +STOLEN_PRIVATE_KEY_2 = SecretStr("some_private_key_2") STOLEN_CREDENTIALS = [ Credentials( - identity=Username(STOLEN_USERNAME_1), - secret=Password(PASSWORD_1), + identity=Username(username=STOLEN_USERNAME_1), + secret=Password(password=PASSWORD_1), ), - Credentials(identity=Username(STOLEN_USERNAME_1), secret=Password(STOLEN_PASSWORD_1)), Credentials( - identity=Username(STOLEN_USERNAME_2), + identity=Username(username=STOLEN_USERNAME_1), secret=Password(password=STOLEN_PASSWORD_1) + ), + Credentials( + identity=Username(username=STOLEN_USERNAME_2), secret=SSHKeypair(public_key=STOLEN_PUBLIC_KEY_1, private_key=STOLEN_PRIVATE_KEY_1), ), Credentials( identity=None, - secret=Password(STOLEN_PASSWORD_2), + secret=Password(password=STOLEN_PASSWORD_2), ), - Credentials(identity=Username(STOLEN_USERNAME_2), secret=LMHash(STOLEN_LM_HASH)), - Credentials(identity=Username(STOLEN_USERNAME_2), secret=NTHash(STOLEN_NT_HASH)), - Credentials(identity=Username(STOLEN_USERNAME_3), secret=None), + Credentials( + identity=Username(username=STOLEN_USERNAME_2), secret=LMHash(lm_hash=STOLEN_LM_HASH) + ), + Credentials( + identity=Username(username=STOLEN_USERNAME_2), secret=NTHash(nt_hash=STOLEN_NT_HASH) + ), + Credentials(identity=Username(username=STOLEN_USERNAME_3), secret=None), ] STOLEN_SSH_KEYS_CREDENTIALS = [ Credentials( - Username(USERNAME), - SSHKeypair(public_key=STOLEN_PUBLIC_KEY_2, private_key=STOLEN_PRIVATE_KEY_2), + identity=Username(username=USERNAME), + secret=SSHKeypair(public_key=STOLEN_PUBLIC_KEY_2, private_key=STOLEN_PRIVATE_KEY_2), ) ] @@ -69,7 +75,7 @@ STOLEN_SSH_KEYS_CREDENTIALS = [ @pytest.fixture def aggregating_credentials_repository() -> AggregatingPropagationCredentialsRepository: control_channel = MagicMock() - control_channel.get_credentials_for_propagation.return_value = CONTROL_CHANNEL_CREDENTIALS + control_channel.get_credentials_for_propagation.return_value = CREDENTIALS return AggregatingPropagationCredentialsRepository(control_channel) diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_powershell_client.py b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_powershell_client.py index 73f7ea64c..2d6d1788f 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_powershell_client.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_powershell_client.py @@ -1,4 +1,5 @@ import pytest +from pydantic import SecretStr from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType from infection_monkey.exploit.powershell_utils.powershell_client import format_password @@ -14,17 +15,17 @@ def test_format_cached_credentials(): def test_format_password(): - expected = "test_password" + expected = SecretStr("test_password") creds = Credentials("test_user", expected, SecretType.PASSWORD) actual = format_password(creds) - assert expected == actual + assert expected.get_secret_value() == actual def test_format_lm_hash(): - lm_hash = "c080132b6f2a0c4e5d1029cc06f48a92" - expected = f"{lm_hash}:00000000000000000000000000000000" + lm_hash = SecretStr("c080132b6f2a0c4e5d1029cc06f48a92") + expected = f"{lm_hash.get_secret_value()}:00000000000000000000000000000000" creds = Credentials("test_user", lm_hash, SecretType.LM_HASH) @@ -34,8 +35,8 @@ def test_format_lm_hash(): def test_format_nt_hash(): - nt_hash = "c080132b6f2a0c4e5d1029cc06f48a92" - expected = f"00000000000000000000000000000000:{nt_hash}" + nt_hash = SecretStr("c080132b6f2a0c4e5d1029cc06f48a92") + expected = f"00000000000000000000000000000000:{nt_hash.get_secret_value()}" creds = Credentials("test_user", nt_hash, SecretType.NT_HASH) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py index 07a29ef95..6727e3fd5 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py @@ -13,13 +13,12 @@ PRIVATE_KEY = "priv_key" @pytest.fixture def credentials_for_test(): - - return Credentials(Username(USERNAME), Password(PASSWORD)) + return Credentials(identity=Username(username=USERNAME), secret=Password(password=PASSWORD)) def test_credential_telem_send(spy_send_telemetry, credentials_for_test): - expected_data = [Credentials.to_mapping(credentials_for_test)] + expected_data = [credentials_for_test.dict(simplify=True)] telem = CredentialsTelem([credentials_for_test]) telem.send() diff --git a/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py b/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py index 010f26b8f..ffd3c3bfa 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py +++ b/monkey/tests/unit_tests/monkey_island/cc/repository/test_mongo_credentials_repository.py @@ -6,14 +6,14 @@ import pytest from pymongo import MongoClient from pymongo.collection import Collection from pymongo.database import Database -from tests.data_for_tests.propagation_credentials import PROPAGATION_CREDENTIALS +from tests.data_for_tests.propagation_credentials import CREDENTIALS from common.credentials import Credentials from monkey_island.cc.repository import MongoCredentialsRepository from monkey_island.cc.server_utils.encryption import ILockableEncryptor -CONFIGURED_CREDENTIALS = PROPAGATION_CREDENTIALS[0:3] -STOLEN_CREDENTIALS = PROPAGATION_CREDENTIALS[3:] +CONFIGURED_CREDENTIALS = CREDENTIALS[0:3] +STOLEN_CREDENTIALS = CREDENTIALS[3:] def reverse(data: bytes) -> bytes: @@ -59,9 +59,9 @@ def test_mongo_repository_get_all(mongo_repository): def test_mongo_repository_configured(mongo_repository): - mongo_repository.save_configured_credentials(PROPAGATION_CREDENTIALS) + mongo_repository.save_configured_credentials(CREDENTIALS) actual_configured_credentials = mongo_repository.get_configured_credentials() - assert actual_configured_credentials == PROPAGATION_CREDENTIALS + assert actual_configured_credentials == CREDENTIALS mongo_repository.remove_configured_credentials() actual_configured_credentials = mongo_repository.get_configured_credentials() @@ -82,7 +82,7 @@ def test_mongo_repository_all(mongo_repository): mongo_repository.save_configured_credentials(CONFIGURED_CREDENTIALS) mongo_repository.save_stolen_credentials(STOLEN_CREDENTIALS) actual_credentials = mongo_repository.get_all_credentials() - assert actual_credentials == PROPAGATION_CREDENTIALS + assert actual_credentials == CREDENTIALS mongo_repository.remove_all_credentials() @@ -91,7 +91,7 @@ def test_mongo_repository_all(mongo_repository): assert mongo_repository.get_configured_credentials() == [] -@pytest.mark.parametrize("credentials", PROPAGATION_CREDENTIALS) +@pytest.mark.parametrize("credentials", CREDENTIALS) def test_configured_secrets_encrypted( mongo_repository: MongoCredentialsRepository, mongo_client: MongoClient, @@ -101,14 +101,14 @@ def test_configured_secrets_encrypted( check_if_stored_credentials_encrypted(mongo_client, credentials) -@pytest.mark.parametrize("credentials", PROPAGATION_CREDENTIALS) +@pytest.mark.parametrize("credentials", CREDENTIALS) def test_stolen_secrets_encrypted(mongo_repository, mongo_client, credentials: Credentials): mongo_repository.save_stolen_credentials([credentials]) check_if_stored_credentials_encrypted(mongo_client, credentials) def check_if_stored_credentials_encrypted(mongo_client: MongoClient, original_credentials): - original_credentials_mapping = Credentials.to_mapping(original_credentials) + original_credentials_mapping = original_credentials.dict(simplify=True) raw_credentials = get_all_credentials_in_mongo(mongo_client) for rc in raw_credentials: @@ -119,6 +119,10 @@ def check_if_stored_credentials_encrypted(mongo_client: MongoClient, original_cr for key, value in credentials_component.items(): assert original_credentials_mapping[identity_or_secret][key] != value.decode() + # Since secrets use the pydantic.SecretType, make sure we're not just storing + # all '*' characters. + assert "***" not in value.decode() + def get_all_credentials_in_mongo( mongo_client: MongoClient, diff --git a/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py b/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py index 0de5c2c91..c48e64564 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py +++ b/monkey/tests/unit_tests/monkey_island/cc/resources/test_propagation_credentials.py @@ -5,15 +5,10 @@ from urllib.parse import urljoin import pytest from tests.common import StubDIContainer -from tests.data_for_tests.propagation_credentials import ( - LM_HASH_CREDENTIALS, - NT_HASH_CREDENTIALS, - PASSWORD_CREDENTIALS_1, - PASSWORD_CREDENTIALS_2, -) +from tests.data_for_tests.propagation_credentials import LM_HASH, NT_HASH, PASSWORD_1, PASSWORD_2 from tests.monkey_island import InMemoryCredentialsRepository -from common.credentials import Credentials +from common.credentials import Credentials, LMHash, NTHash, Password from monkey_island.cc.repository import ICredentialsRepository from monkey_island.cc.resources import PropagationCredentials from monkey_island.cc.resources.propagation_credentials import ( @@ -24,6 +19,10 @@ from monkey_island.cc.resources.propagation_credentials import ( ALL_CREDENTIALS_URL = PropagationCredentials.urls[0] CONFIGURED_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL + "/", _configured_collection) STOLEN_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL + "/", _stolen_collection) +CREDENTIALS_1 = Credentials(identity=None, secret=Password(password=PASSWORD_1)) +CREDENTIALS_2 = Credentials(identity=None, secret=LMHash(lm_hash=LM_HASH)) +CREDENTIALS_3 = Credentials(identity=None, secret=NTHash(nt_hash=NT_HASH)) +CREDENTIALS_4 = Credentials(identity=None, secret=Password(password=PASSWORD_2)) @pytest.fixture @@ -42,20 +41,18 @@ def flask_client(build_flask_client, credentials_repository): def test_propagation_credentials_endpoint_get(flask_client, credentials_repository): - credentials_repository.save_configured_credentials( - [PASSWORD_CREDENTIALS_1, NT_HASH_CREDENTIALS] - ) - credentials_repository.save_stolen_credentials([LM_HASH_CREDENTIALS, PASSWORD_CREDENTIALS_2]) + credentials_repository.save_configured_credentials([CREDENTIALS_1, CREDENTIALS_2]) + credentials_repository.save_stolen_credentials([CREDENTIALS_3, CREDENTIALS_4]) resp = flask_client.get(ALL_CREDENTIALS_URL) - actual_propagation_credentials = [Credentials.from_mapping(creds) for creds in resp.json] + actual_propagation_credentials = [Credentials(**creds) for creds in resp.json] assert resp.status_code == HTTPStatus.OK assert len(actual_propagation_credentials) == 4 - assert PASSWORD_CREDENTIALS_1 in actual_propagation_credentials - assert LM_HASH_CREDENTIALS in actual_propagation_credentials - assert NT_HASH_CREDENTIALS in actual_propagation_credentials - assert PASSWORD_CREDENTIALS_2 in actual_propagation_credentials + assert CREDENTIALS_1 in actual_propagation_credentials + assert CREDENTIALS_2 in actual_propagation_credentials + assert CREDENTIALS_3 in actual_propagation_credentials + assert CREDENTIALS_4 in actual_propagation_credentials def pre_populate_repository( @@ -69,24 +66,22 @@ def pre_populate_repository( @pytest.mark.parametrize("url", [CONFIGURED_CREDENTIALS_URL, STOLEN_CREDENTIALS_URL]) def test_propagation_credentials_endpoint__get_stolen(flask_client, credentials_repository, url): - pre_populate_repository( - url, credentials_repository, [PASSWORD_CREDENTIALS_1, LM_HASH_CREDENTIALS] - ) + pre_populate_repository(url, credentials_repository, [CREDENTIALS_1, CREDENTIALS_2]) resp = flask_client.get(url) - actual_propagation_credentials = [Credentials.from_mapping(creds) for creds in resp.json] + actual_propagation_credentials = [Credentials(**creds) for creds in resp.json] assert resp.status_code == HTTPStatus.OK assert len(actual_propagation_credentials) == 2 - assert actual_propagation_credentials[0] == PASSWORD_CREDENTIALS_1 - assert actual_propagation_credentials[1] == LM_HASH_CREDENTIALS + assert actual_propagation_credentials[0].secret.password == PASSWORD_1 + assert actual_propagation_credentials[1].secret.lm_hash == LM_HASH def test_configured_propagation_credentials_endpoint_put(flask_client, credentials_repository): pre_populate_repository( CONFIGURED_CREDENTIALS_URL, credentials_repository, - [PASSWORD_CREDENTIALS_1, LM_HASH_CREDENTIALS], + [CREDENTIALS_1, CREDENTIALS_2], ) resp = flask_client.put(CONFIGURED_CREDENTIALS_URL, json=[]) assert resp.status_code == HTTPStatus.NO_CONTENT diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_format_credentials.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_format_credentials.py index bb51b89dd..7544e8b48 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_format_credentials.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_format_credentials.py @@ -1,64 +1,29 @@ -from common.credentials import ( - CredentialComponentType, - Credentials, - LMHash, - NTHash, - Password, - SSHKeypair, - Username, -) +from tests.data_for_tests.propagation_credentials import FULL_CREDENTIALS, USERNAME + from monkey_island.cc.services.reporting import format_creds_for_reporting -monkey_hostname = "fake_hostname" -fake_monkey_guid = "abc" - -fake_username = Username("m0nk3y_user") -fake_nt_hash = NTHash("AEBD4DE384C7EC43AAD3B435B51404EE") -fake_lm_hash = LMHash("7A21990FCD3D759941E45C490F143D5F") -fake_password = Password("trytostealthis") -fake_ssh_public_key = "RSA_public_key" -fake_ssh_private_key = "RSA_private_key" -fake_ssh_key = SSHKeypair(fake_ssh_private_key, fake_ssh_public_key) - -identities = (fake_username,) -secrets = (fake_nt_hash, fake_lm_hash, fake_password, fake_ssh_key) - -fake_credentials = [ - Credentials(fake_username, fake_nt_hash), - Credentials(fake_username, fake_lm_hash), - Credentials(fake_username, fake_password), - Credentials(fake_username, fake_ssh_key), - Credentials(None, fake_ssh_key), - Credentials(fake_username, None), -] - def test_formatting_credentials_for_report(): - credentials = format_creds_for_reporting(fake_credentials) + credentials = format_creds_for_reporting(FULL_CREDENTIALS) result1 = { - "_type": CredentialComponentType.NT_HASH.name, "type": "NTLM hash", - "username": fake_username.username, + "username": USERNAME, } result2 = { - "_type": CredentialComponentType.LM_HASH.name, "type": "LM hash", - "username": fake_username.username, + "username": USERNAME, } result3 = { - "_type": CredentialComponentType.PASSWORD.name, "type": "Clear Password", - "username": fake_username.username, + "username": USERNAME, } result4 = { - "_type": CredentialComponentType.SSH_KEYPAIR.name, "type": "Clear SSH private key", - "username": fake_username.username, + "username": USERNAME, } result5 = { - "_type": CredentialComponentType.SSH_KEYPAIR.name, "type": "Clear SSH private key", "username": "", } diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 42d46716f..caf7c3e24 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -7,8 +7,7 @@ from common.agent_configuration.agent_sub_configurations import ( CustomPBAConfiguration, ScanTargetConfiguration, ) -from common.credentials import Credentials -from common.utils import IJSONSerializable +from common.credentials import Credentials, LMHash, NTHash from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue from monkey_island.cc.models import Report @@ -166,6 +165,9 @@ LDAPServerFactory.buildProtocol get_file_sha256_hash strict_slashes # unused attribute (monkey/monkey_island/cc/app.py:96) post_breach_actions # unused variable (monkey\infection_monkey\config.py:95) +LMHash.validate_hash_format +NTHash.validate_hash_format +Credentials.Config.json_encoders # Deployments DEVELOP # unused variable (monkey/monkey/monkey_island/cc/deployment.py:5) @@ -314,8 +316,9 @@ EXPLOITED CC CC_TUNNEL -Credentials.from_json -IJSONSerializable.from_json +# TODO Remove with #2217 +IJSONSerializable +from_json IslandEventTopic.AGENT_CONNECTED IslandEventTopic.CLEAR_SIMULATION_DATA