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:
parent
8e332e5285
commit
2b245b34cb
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
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 import Schema, fields, post_load, pre_dump
|
||||||
from marshmallow.exceptions import MarshmallowError
|
from marshmallow.exceptions import MarshmallowError
|
||||||
|
@ -42,27 +42,15 @@ CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA = {
|
||||||
|
|
||||||
|
|
||||||
class CredentialsSchema(Schema):
|
class CredentialsSchema(Schema):
|
||||||
# Use fields.List instead of fields.Tuple because marshmallow requires fields.Tuple to have a
|
identity = fields.Mapping()
|
||||||
# fixed length.
|
secret = fields.Mapping()
|
||||||
identities = fields.List(fields.Mapping())
|
|
||||||
secrets = fields.List(fields.Mapping())
|
|
||||||
|
|
||||||
@post_load
|
@post_load
|
||||||
def _make_credentials(
|
def _make_credentials(
|
||||||
self, data: MutableMapping, **kwargs: Mapping[str, Any]
|
self, data: MutableMapping, **kwargs: Mapping[str, Any]
|
||||||
) -> Mapping[str, Sequence[Mapping[str, Any]]]:
|
) -> Mapping[str, Sequence[Mapping[str, Any]]]:
|
||||||
data["identities"] = tuple(
|
data["identity"] = CredentialsSchema._build_credential_component(data["identity"])
|
||||||
[
|
data["secret"] = CredentialsSchema._build_credential_component(data["secret"])
|
||||||
CredentialsSchema._build_credential_component(component)
|
|
||||||
for component in data["identities"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
data["secrets"] = tuple(
|
|
||||||
[
|
|
||||||
CredentialsSchema._build_credential_component(component)
|
|
||||||
for component in data["secrets"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -89,18 +77,8 @@ class CredentialsSchema(Schema):
|
||||||
) -> Mapping[str, Sequence[Mapping[str, Any]]]:
|
) -> Mapping[str, Sequence[Mapping[str, Any]]]:
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
data["identities"] = tuple(
|
data["identity"] = CredentialsSchema._serialize_credential_component(credentials.identity)
|
||||||
[
|
data["secret"] = CredentialsSchema._serialize_credential_component(credentials.secret)
|
||||||
CredentialsSchema._serialize_credential_component(component)
|
|
||||||
for component in credentials.identities
|
|
||||||
]
|
|
||||||
)
|
|
||||||
data["secrets"] = tuple(
|
|
||||||
[
|
|
||||||
CredentialsSchema._serialize_credential_component(component)
|
|
||||||
for component in credentials.secrets
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -117,8 +95,8 @@ class CredentialsSchema(Schema):
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Credentials(IJSONSerializable):
|
class Credentials(IJSONSerializable):
|
||||||
identities: Tuple[ICredentialComponent]
|
identity: ICredentialComponent
|
||||||
secrets: Tuple[ICredentialComponent]
|
secret: ICredentialComponent
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_mapping(credentials: Mapping) -> Credentials:
|
def from_mapping(credentials: Mapping) -> Credentials:
|
||||||
|
|
|
@ -6,21 +6,8 @@ nt_hash = "C1C58F96CDF212B50837BC11A00BE47C"
|
||||||
lm_hash = "299BD128C1101FD6299BD128C1101FD6"
|
lm_hash = "299BD128C1101FD6299BD128C1101FD6"
|
||||||
password_1 = "trytostealthis"
|
password_1 = "trytostealthis"
|
||||||
password_2 = "password"
|
password_2 = "password"
|
||||||
password_3 = "12345678"
|
|
||||||
|
|
||||||
PROPAGATION_CREDENTIALS_1 = Credentials(
|
PROPAGATION_CREDENTIALS_1 = Credentials(identity=Username(username), secret=Password(password_1))
|
||||||
identities=(Username(username),),
|
PROPAGATION_CREDENTIALS_2 = Credentials(identity=Username(special_username), secret=LMHash(lm_hash))
|
||||||
secrets=(NTHash(nt_hash), LMHash(lm_hash), Password(password_1)),
|
PROPAGATION_CREDENTIALS_3 = Credentials(identity=Username(username), secret=NTHash(nt_hash))
|
||||||
)
|
PROPAGATION_CREDENTIALS_4 = Credentials(identity=Username(username), secret=Password(password_2))
|
||||||
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),),
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -14,7 +15,6 @@ from common.credentials import (
|
||||||
)
|
)
|
||||||
|
|
||||||
USER1 = "test_user_1"
|
USER1 = "test_user_1"
|
||||||
USER2 = "test_user_2"
|
|
||||||
PASSWORD = "12435"
|
PASSWORD = "12435"
|
||||||
LM_HASH = "AEBD4DE384C7EC43AAD3B435B51404EE"
|
LM_HASH = "AEBD4DE384C7EC43AAD3B435B51404EE"
|
||||||
NT_HASH = "7A21990FCD3D759941E45C490F143D5F"
|
NT_HASH = "7A21990FCD3D759941E45C490F143D5F"
|
||||||
|
@ -22,11 +22,19 @@ PUBLIC_KEY = "MY_PUBLIC_KEY"
|
||||||
PRIVATE_KEY = "MY_PRIVATE_KEY"
|
PRIVATE_KEY = "MY_PRIVATE_KEY"
|
||||||
|
|
||||||
CREDENTIALS_DICT = {
|
CREDENTIALS_DICT = {
|
||||||
"identities": [
|
"identity": {"credential_type": "USERNAME", "username": USER1},
|
||||||
{"credential_type": "USERNAME", "username": USER1},
|
"secret": {},
|
||||||
{"credential_type": "USERNAME", "username": USER2},
|
}
|
||||||
],
|
|
||||||
"secrets": [
|
IDENTITY = Username(USER1)
|
||||||
|
SECRETS = (
|
||||||
|
Password(PASSWORD),
|
||||||
|
LMHash(LM_HASH),
|
||||||
|
NTHash(NT_HASH),
|
||||||
|
SSHKeypair(PRIVATE_KEY, PUBLIC_KEY),
|
||||||
|
)
|
||||||
|
|
||||||
|
SECRETS_DICTS = [
|
||||||
{"credential_type": "PASSWORD", "password": PASSWORD},
|
{"credential_type": "PASSWORD", "password": PASSWORD},
|
||||||
{"credential_type": "LM_HASH", "lm_hash": LM_HASH},
|
{"credential_type": "LM_HASH", "lm_hash": LM_HASH},
|
||||||
{"credential_type": "NT_HASH", "nt_hash": NT_HASH},
|
{"credential_type": "NT_HASH", "nt_hash": NT_HASH},
|
||||||
|
@ -35,61 +43,72 @@ CREDENTIALS_DICT = {
|
||||||
"public_key": PUBLIC_KEY,
|
"public_key": PUBLIC_KEY,
|
||||||
"private_key": PRIVATE_KEY,
|
"private_key": PRIVATE_KEY,
|
||||||
},
|
},
|
||||||
],
|
]
|
||||||
}
|
|
||||||
|
|
||||||
CREDENTIALS_JSON = json.dumps(CREDENTIALS_DICT)
|
|
||||||
|
|
||||||
IDENTITIES = (Username(USER1), Username(USER2))
|
|
||||||
SECRETS = (
|
|
||||||
Password(PASSWORD),
|
|
||||||
LMHash(LM_HASH),
|
|
||||||
NTHash(NT_HASH),
|
|
||||||
SSHKeypair(PRIVATE_KEY, PUBLIC_KEY),
|
|
||||||
)
|
|
||||||
CREDENTIALS_OBJECT = Credentials(IDENTITIES, SECRETS)
|
|
||||||
|
|
||||||
|
|
||||||
def test_credentials_serialization_json():
|
@pytest.mark.parametrize("secret, expected_secret", zip(SECRETS, SECRETS_DICTS))
|
||||||
serialized_credentials = Credentials.to_json(CREDENTIALS_OBJECT)
|
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():
|
@pytest.mark.parametrize("secret, expected_secret", zip(SECRETS, SECRETS_DICTS))
|
||||||
serialized_credentials = Credentials.to_mapping(CREDENTIALS_OBJECT)
|
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():
|
@pytest.mark.parametrize("secret, secret_dict", zip(SECRETS, SECRETS_DICTS))
|
||||||
deserialized_credentials = Credentials.from_mapping(CREDENTIALS_DICT)
|
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():
|
@pytest.mark.parametrize("secret, secret_dict", zip(SECRETS, SECRETS_DICTS))
|
||||||
deserialized_credentials = Credentials.from_json(CREDENTIALS_JSON)
|
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():
|
def test_credentials_deserialization__invalid_credentials():
|
||||||
invalid_data = {"secrets": [], "unknown_key": []}
|
invalid_data = {"secret": SECRETS_DICTS[0], "unknown_key": []}
|
||||||
with pytest.raises(InvalidCredentialsError):
|
with pytest.raises(InvalidCredentialsError):
|
||||||
Credentials.from_mapping(invalid_data)
|
Credentials.from_mapping(invalid_data)
|
||||||
|
|
||||||
|
|
||||||
def test_credentials_deserialization__invalid_component_type():
|
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):
|
with pytest.raises(InvalidCredentialsError):
|
||||||
Credentials.from_mapping(invalid_data)
|
Credentials.from_mapping(invalid_data)
|
||||||
|
|
||||||
|
|
||||||
def test_credentials_deserialization__invalid_component():
|
def test_credentials_deserialization__invalid_component():
|
||||||
invalid_data = {
|
invalid_data = {
|
||||||
"secrets": [],
|
"secret": SECRETS_DICTS[0],
|
||||||
"identities": [{"credential_type": "USERNAME", "unknown_field": "user1"}],
|
"identity": {"credential_type": "USERNAME", "unknown_field": "user1"},
|
||||||
}
|
}
|
||||||
with pytest.raises(InvalidCredentialComponentError):
|
with pytest.raises(InvalidCredentialComponentError):
|
||||||
Credentials.from_mapping(invalid_data)
|
Credentials.from_mapping(invalid_data)
|
||||||
|
|
Loading…
Reference in New Issue