Island: Simplify Credentials

Storing a sequence of identities and secrets in Credentials objects
added a lot of complication. From now on, a Credentials object consists
of one identity and one secret. See #2072.
This commit is contained in:
Mike Salvatore 2022-07-14 13:10:24 -04:00
parent 8e332e5285
commit 2b245b34cb
3 changed files with 67 additions and 83 deletions

View File

@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping, MutableMapping, Sequence, Tuple
from typing import Any, Mapping, MutableMapping, Sequence
from marshmallow import Schema, fields, post_load, pre_dump
from marshmallow.exceptions import MarshmallowError
@ -42,27 +42,15 @@ CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA = {
class CredentialsSchema(Schema):
# Use fields.List instead of fields.Tuple because marshmallow requires fields.Tuple to have a
# fixed length.
identities = fields.List(fields.Mapping())
secrets = fields.List(fields.Mapping())
identity = fields.Mapping()
secret = fields.Mapping()
@post_load
def _make_credentials(
self, data: MutableMapping, **kwargs: Mapping[str, Any]
) -> Mapping[str, Sequence[Mapping[str, Any]]]:
data["identities"] = tuple(
[
CredentialsSchema._build_credential_component(component)
for component in data["identities"]
]
)
data["secrets"] = tuple(
[
CredentialsSchema._build_credential_component(component)
for component in data["secrets"]
]
)
data["identity"] = CredentialsSchema._build_credential_component(data["identity"])
data["secret"] = CredentialsSchema._build_credential_component(data["secret"])
return data
@ -89,18 +77,8 @@ class CredentialsSchema(Schema):
) -> Mapping[str, Sequence[Mapping[str, Any]]]:
data = {}
data["identities"] = tuple(
[
CredentialsSchema._serialize_credential_component(component)
for component in credentials.identities
]
)
data["secrets"] = tuple(
[
CredentialsSchema._serialize_credential_component(component)
for component in credentials.secrets
]
)
data["identity"] = CredentialsSchema._serialize_credential_component(credentials.identity)
data["secret"] = CredentialsSchema._serialize_credential_component(credentials.secret)
return data
@ -117,8 +95,8 @@ class CredentialsSchema(Schema):
@dataclass(frozen=True)
class Credentials(IJSONSerializable):
identities: Tuple[ICredentialComponent]
secrets: Tuple[ICredentialComponent]
identity: ICredentialComponent
secret: ICredentialComponent
@staticmethod
def from_mapping(credentials: Mapping) -> Credentials:

View File

@ -6,21 +6,8 @@ nt_hash = "C1C58F96CDF212B50837BC11A00BE47C"
lm_hash = "299BD128C1101FD6299BD128C1101FD6"
password_1 = "trytostealthis"
password_2 = "password"
password_3 = "12345678"
PROPAGATION_CREDENTIALS_1 = Credentials(
identities=(Username(username),),
secrets=(NTHash(nt_hash), LMHash(lm_hash), Password(password_1)),
)
PROPAGATION_CREDENTIALS_2 = Credentials(
identities=(Username(username), Username(special_username)),
secrets=(Password(password_1), Password(password_2), Password(password_3)),
)
PROPAGATION_CREDENTIALS_3 = Credentials(
identities=(Username(username),),
secrets=(Password(password_1),),
)
PROPAGATION_CREDENTIALS_4 = Credentials(
identities=(Username(username),),
secrets=(Password(password_2),),
)
PROPAGATION_CREDENTIALS_1 = Credentials(identity=Username(username), secret=Password(password_1))
PROPAGATION_CREDENTIALS_2 = Credentials(identity=Username(special_username), secret=LMHash(lm_hash))
PROPAGATION_CREDENTIALS_3 = Credentials(identity=Username(username), secret=NTHash(nt_hash))
PROPAGATION_CREDENTIALS_4 = Credentials(identity=Username(username), secret=Password(password_2))

View File

@ -1,3 +1,4 @@
import copy
import json
import pytest
@ -14,7 +15,6 @@ from common.credentials import (
)
USER1 = "test_user_1"
USER2 = "test_user_2"
PASSWORD = "12435"
LM_HASH = "AEBD4DE384C7EC43AAD3B435B51404EE"
NT_HASH = "7A21990FCD3D759941E45C490F143D5F"
@ -22,74 +22,93 @@ PUBLIC_KEY = "MY_PUBLIC_KEY"
PRIVATE_KEY = "MY_PRIVATE_KEY"
CREDENTIALS_DICT = {
"identities": [
{"credential_type": "USERNAME", "username": USER1},
{"credential_type": "USERNAME", "username": USER2},
],
"secrets": [
{"credential_type": "PASSWORD", "password": PASSWORD},
{"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,
},
],
"identity": {"credential_type": "USERNAME", "username": USER1},
"secret": {},
}
CREDENTIALS_JSON = json.dumps(CREDENTIALS_DICT)
IDENTITIES = (Username(USER1), Username(USER2))
IDENTITY = Username(USER1)
SECRETS = (
Password(PASSWORD),
LMHash(LM_HASH),
NTHash(NT_HASH),
SSHKeypair(PRIVATE_KEY, PUBLIC_KEY),
)
CREDENTIALS_OBJECT = Credentials(IDENTITIES, SECRETS)
SECRETS_DICTS = [
{"credential_type": "PASSWORD", "password": PASSWORD},
{"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,
},
]
def test_credentials_serialization_json():
serialized_credentials = Credentials.to_json(CREDENTIALS_OBJECT)
@pytest.mark.parametrize("secret, expected_secret", zip(SECRETS, SECRETS_DICTS))
def test_credentials_serialization_json(secret, expected_secret):
expected_credentials = copy.copy(CREDENTIALS_DICT)
expected_credentials["secret"] = expected_secret
c = Credentials(IDENTITY, secret)
assert json.loads(serialized_credentials) == CREDENTIALS_DICT
serialized_credentials = Credentials.to_json(c)
assert json.loads(serialized_credentials) == expected_credentials
def test_credentials_serialization_mapping():
serialized_credentials = Credentials.to_mapping(CREDENTIALS_OBJECT)
@pytest.mark.parametrize("secret, expected_secret", zip(SECRETS, SECRETS_DICTS))
def test_credentials_serialization_mapping(secret, expected_secret):
expected_credentials = copy.copy(CREDENTIALS_DICT)
expected_credentials["secret"] = expected_secret
c = Credentials(IDENTITY, secret)
assert serialized_credentials == CREDENTIALS_DICT
serialized_credentials = Credentials.to_mapping(c)
assert serialized_credentials == expected_credentials
def test_credentials_deserialization__from_mapping():
deserialized_credentials = Credentials.from_mapping(CREDENTIALS_DICT)
@pytest.mark.parametrize("secret, secret_dict", zip(SECRETS, SECRETS_DICTS))
def test_credentials_deserialization__from_mapping(secret, secret_dict):
expected_credentials = Credentials(IDENTITY, secret)
credentials_dict = copy.copy(CREDENTIALS_DICT)
credentials_dict["secret"] = secret_dict
assert deserialized_credentials == CREDENTIALS_OBJECT
deserialized_credentials = Credentials.from_mapping(credentials_dict)
assert deserialized_credentials == expected_credentials
def test_credentials_deserialization__from_json():
deserialized_credentials = Credentials.from_json(CREDENTIALS_JSON)
@pytest.mark.parametrize("secret, secret_dict", zip(SECRETS, SECRETS_DICTS))
def test_credentials_deserialization__from_json(secret, secret_dict):
expected_credentials = Credentials(IDENTITY, secret)
credentials_dict = copy.copy(CREDENTIALS_DICT)
credentials_dict["secret"] = secret_dict
assert deserialized_credentials == CREDENTIALS_OBJECT
deserialized_credentials = Credentials.from_json(json.dumps(credentials_dict))
assert deserialized_credentials == expected_credentials
def test_credentials_deserialization__invalid_credentials():
invalid_data = {"secrets": [], "unknown_key": []}
invalid_data = {"secret": SECRETS_DICTS[0], "unknown_key": []}
with pytest.raises(InvalidCredentialsError):
Credentials.from_mapping(invalid_data)
def test_credentials_deserialization__invalid_component_type():
invalid_data = {"secrets": [], "identities": [{"credential_type": "FAKE", "username": "user1"}]}
invalid_data = {
"secret": SECRETS_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 = {
"secrets": [],
"identities": [{"credential_type": "USERNAME", "unknown_field": "user1"}],
"secret": SECRETS_DICTS[0],
"identity": {"credential_type": "USERNAME", "unknown_field": "user1"},
}
with pytest.raises(InvalidCredentialComponentError):
Credentials.from_mapping(invalid_data)