pyoaev 1.18.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docs/conf.py +65 -0
- pyoaev/__init__.py +26 -0
- pyoaev/_version.py +6 -0
- pyoaev/apis/__init__.py +20 -0
- pyoaev/apis/attack_pattern.py +28 -0
- pyoaev/apis/collector.py +29 -0
- pyoaev/apis/cve.py +18 -0
- pyoaev/apis/document.py +29 -0
- pyoaev/apis/endpoint.py +38 -0
- pyoaev/apis/inject.py +29 -0
- pyoaev/apis/inject_expectation/__init__.py +1 -0
- pyoaev/apis/inject_expectation/inject_expectation.py +118 -0
- pyoaev/apis/inject_expectation/model/__init__.py +7 -0
- pyoaev/apis/inject_expectation/model/expectation.py +173 -0
- pyoaev/apis/inject_expectation_trace.py +36 -0
- pyoaev/apis/injector.py +26 -0
- pyoaev/apis/injector_contract.py +56 -0
- pyoaev/apis/inputs/__init__.py +0 -0
- pyoaev/apis/inputs/search.py +72 -0
- pyoaev/apis/kill_chain_phase.py +22 -0
- pyoaev/apis/me.py +17 -0
- pyoaev/apis/organization.py +11 -0
- pyoaev/apis/payload.py +27 -0
- pyoaev/apis/security_platform.py +33 -0
- pyoaev/apis/tag.py +19 -0
- pyoaev/apis/team.py +25 -0
- pyoaev/apis/user.py +31 -0
- pyoaev/backends/__init__.py +14 -0
- pyoaev/backends/backend.py +136 -0
- pyoaev/backends/protocol.py +32 -0
- pyoaev/base.py +320 -0
- pyoaev/client.py +596 -0
- pyoaev/configuration/__init__.py +3 -0
- pyoaev/configuration/configuration.py +188 -0
- pyoaev/configuration/sources.py +44 -0
- pyoaev/contracts/__init__.py +5 -0
- pyoaev/contracts/contract_builder.py +44 -0
- pyoaev/contracts/contract_config.py +292 -0
- pyoaev/contracts/contract_utils.py +22 -0
- pyoaev/contracts/variable_helper.py +124 -0
- pyoaev/daemons/__init__.py +4 -0
- pyoaev/daemons/base_daemon.py +131 -0
- pyoaev/daemons/collector_daemon.py +91 -0
- pyoaev/exceptions.py +219 -0
- pyoaev/helpers.py +451 -0
- pyoaev/mixins.py +242 -0
- pyoaev/signatures/__init__.py +0 -0
- pyoaev/signatures/signature_match.py +12 -0
- pyoaev/signatures/signature_type.py +51 -0
- pyoaev/signatures/types.py +17 -0
- pyoaev/utils.py +211 -0
- pyoaev-1.18.20.dist-info/METADATA +134 -0
- pyoaev-1.18.20.dist-info/RECORD +72 -0
- pyoaev-1.18.20.dist-info/WHEEL +5 -0
- pyoaev-1.18.20.dist-info/licenses/LICENSE +201 -0
- pyoaev-1.18.20.dist-info/top_level.txt +4 -0
- scripts/release.py +127 -0
- test/__init__.py +0 -0
- test/apis/__init__.py +0 -0
- test/apis/expectation/__init__.py +0 -0
- test/apis/expectation/test_expectation.py +338 -0
- test/apis/injector_contract/__init__.py +0 -0
- test/apis/injector_contract/test_injector_contract.py +58 -0
- test/configuration/__init__.py +0 -0
- test/configuration/test_configuration.py +257 -0
- test/configuration/test_sources.py +69 -0
- test/daemons/__init__.py +0 -0
- test/daemons/test_base_daemon.py +109 -0
- test/daemons/test_collector_daemon.py +39 -0
- test/signatures/__init__.py +0 -0
- test/signatures/test_signature_match.py +25 -0
- test/signatures/test_signature_type.py +57 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import os.path
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from pyoaev.configuration.sources import DictionarySource, EnvironmentSource
|
|
9
|
+
|
|
10
|
+
CONFIGURATION_TYPES = str | int | bool | Any | None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_truthy(value: str) -> bool:
|
|
14
|
+
"""Asserts whether a given string signals a "True" value
|
|
15
|
+
|
|
16
|
+
:param value: value to test
|
|
17
|
+
:type value: str
|
|
18
|
+
|
|
19
|
+
:return: whether the string represents True or not.
|
|
20
|
+
:rtype: bool
|
|
21
|
+
"""
|
|
22
|
+
return value.lower() in ["yes", "true"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_falsy(value: str) -> bool:
|
|
26
|
+
"""Asserts whether a given string signals a "False" value
|
|
27
|
+
|
|
28
|
+
:param value: value to test
|
|
29
|
+
:type value: str
|
|
30
|
+
|
|
31
|
+
:return: whether the string represents False or not.
|
|
32
|
+
:rtype: bool
|
|
33
|
+
"""
|
|
34
|
+
return value.lower() in ["no", "false"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ConfigurationHint(BaseModel):
|
|
38
|
+
"""An individual configuration hint. This allows for specifying
|
|
39
|
+
where any given configuration key can be found, in env vars,
|
|
40
|
+
config files. Additionally, it may define a default value or
|
|
41
|
+
a discrete override value.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
data: Optional[CONFIGURATION_TYPES] = Field(default=None)
|
|
45
|
+
"""Override value; when set, getting the configuration value for
|
|
46
|
+
the key described in this instance returns this value.
|
|
47
|
+
"""
|
|
48
|
+
env: Optional[str] = Field(default=None)
|
|
49
|
+
"""Defines which env var should be read for getting the value
|
|
50
|
+
"""
|
|
51
|
+
file_path: Optional[list[str]] = Field(default=None)
|
|
52
|
+
"""Defines a JSON path (nested keys) to follow in the provided
|
|
53
|
+
config file for reaching the value.
|
|
54
|
+
|
|
55
|
+
Example: ["toplevel", "subkey"] will hint for searching for
|
|
56
|
+
the config key at { "toplevel": { "subkey": { "config_key"}}
|
|
57
|
+
"""
|
|
58
|
+
is_number: Optional[bool] = Field(default=False)
|
|
59
|
+
"""Hints at whteher the configuration value should be
|
|
60
|
+
interpreted as a number.
|
|
61
|
+
"""
|
|
62
|
+
default: Optional[CONFIGURATION_TYPES] = Field(default=None)
|
|
63
|
+
"""When defined, provides a default value for whenever none of the
|
|
64
|
+
hinted locations or the data field have a value.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Configuration:
|
|
69
|
+
"""A configuration object providing ways to an interface for getting
|
|
70
|
+
configuration values. It should be provided with a collection of hints
|
|
71
|
+
to enable its behaviour.
|
|
72
|
+
|
|
73
|
+
:param config_hints: a dictionary of hints, for which the key is the
|
|
74
|
+
desired configuration key (e.g. "log_level") and the value is either
|
|
75
|
+
a dictionary of hints (see ConfigurationHint) or a standalone string.
|
|
76
|
+
In the latter case, the string will be interpreted as a default value.
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
.. code-block:: python
|
|
80
|
+
{
|
|
81
|
+
"my_config_key": {
|
|
82
|
+
"env" : "MY_CONFIG_VALUE_ENV_VAR",
|
|
83
|
+
"file_path": ["first_level", "second_level"]
|
|
84
|
+
},
|
|
85
|
+
"my_other_config_key: "discrete value"
|
|
86
|
+
}
|
|
87
|
+
:type config_hints: Dict[str, dict | str]
|
|
88
|
+
:param config_values: dictionary of config values to preemptively load into the
|
|
89
|
+
Configuration object. The format of this dictionary should follow the patterns
|
|
90
|
+
chosen in the file_path property of ConfigurationHint object passed
|
|
91
|
+
as config_hints, defaults to None
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
.. code-block:: python
|
|
95
|
+
{
|
|
96
|
+
"first_level": {
|
|
97
|
+
"second_level": {
|
|
98
|
+
"my_config_key": "some value"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
:type config_values: dict (json), optional
|
|
103
|
+
:param config_file_path: path to the configuration file. The file should
|
|
104
|
+
contain a json structure that matches the format of the config_values param,
|
|
105
|
+
defaults to './config.yml' (relative path).
|
|
106
|
+
:type config_file_path: str
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
config_hints: Dict[str, dict | str],
|
|
112
|
+
config_values: dict = None,
|
|
113
|
+
config_file_path: str = os.path.join(os.curdir, "config.yml"),
|
|
114
|
+
):
|
|
115
|
+
self.__config_hints = {
|
|
116
|
+
key: (
|
|
117
|
+
ConfigurationHint(**value)
|
|
118
|
+
if isinstance(value, dict)
|
|
119
|
+
else ConfigurationHint(**{"default": value})
|
|
120
|
+
)
|
|
121
|
+
for key, value in config_hints.items()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
file_contents = (
|
|
125
|
+
yaml.load(open(config_file_path), Loader=yaml.FullLoader)
|
|
126
|
+
if os.path.isfile(config_file_path)
|
|
127
|
+
else {}
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
self.__config_values = (config_values or {}) | file_contents
|
|
131
|
+
|
|
132
|
+
def get(self, config_key: str) -> CONFIGURATION_TYPES:
|
|
133
|
+
"""Gets the value pointed to by the configuration key. If the key is defined
|
|
134
|
+
with actual hints (as opposed to a discrete value), it will use those hints to
|
|
135
|
+
potentially find a value. If the key was not defined as part of the supplied
|
|
136
|
+
config_hints, this will always return None.
|
|
137
|
+
|
|
138
|
+
:param config_key: the configuration key to search a value for.
|
|
139
|
+
:type config_key: str
|
|
140
|
+
|
|
141
|
+
:return: the value pointed to by the configuration key, or None if not found
|
|
142
|
+
:rtype: CONFIGURATION_TYPES
|
|
143
|
+
"""
|
|
144
|
+
config = self.__config_hints.get(config_key)
|
|
145
|
+
if config is None:
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
return self.__process_value_to_type(
|
|
149
|
+
config.data or self.__dig_config_sources_for_key(config), config.is_number
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def set(self, config_key: str, value: CONFIGURATION_TYPES):
|
|
153
|
+
"""Sets an arbitrary value in the Configuration object, for
|
|
154
|
+
the supplied configuration key, after which any request for the value
|
|
155
|
+
of that key will return this new value.
|
|
156
|
+
|
|
157
|
+
:param config_key: the configuration key to set a value for.
|
|
158
|
+
:type config_key: str
|
|
159
|
+
:param value: the new value to set for the configuration key.
|
|
160
|
+
:type value: CONFIGURATION_TYPES
|
|
161
|
+
"""
|
|
162
|
+
if config_key not in self.__config_hints:
|
|
163
|
+
self.__config_hints[config_key] = ConfigurationHint(**{"data": value})
|
|
164
|
+
else:
|
|
165
|
+
self.__config_hints[config_key].data = value
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def __process_value_to_type(value: CONFIGURATION_TYPES, is_number_hint: bool):
|
|
169
|
+
if value is None:
|
|
170
|
+
return value
|
|
171
|
+
if isinstance(value, int) or is_number_hint:
|
|
172
|
+
return int(value)
|
|
173
|
+
if isinstance(value, str):
|
|
174
|
+
if is_truthy(value):
|
|
175
|
+
return True
|
|
176
|
+
if is_falsy(value):
|
|
177
|
+
return False
|
|
178
|
+
if len(value) == 0:
|
|
179
|
+
return None
|
|
180
|
+
return value
|
|
181
|
+
|
|
182
|
+
def __dig_config_sources_for_key(
|
|
183
|
+
self, config: ConfigurationHint
|
|
184
|
+
) -> CONFIGURATION_TYPES:
|
|
185
|
+
result = EnvironmentSource.get(config.env) or DictionarySource.get(
|
|
186
|
+
config.file_path, self.__config_values
|
|
187
|
+
)
|
|
188
|
+
return result or config.default
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EnvironmentSource:
|
|
5
|
+
"""A utility for fecthing a value in the env vars."""
|
|
6
|
+
|
|
7
|
+
@classmethod
|
|
8
|
+
def get(cls, env_var: str) -> str | None:
|
|
9
|
+
"""Gets the value for the specified env var
|
|
10
|
+
|
|
11
|
+
:param env_var: the name of the env var to query
|
|
12
|
+
:type env_var: str
|
|
13
|
+
|
|
14
|
+
:return: value of the env var, or None if not found
|
|
15
|
+
:rtype: str | None
|
|
16
|
+
"""
|
|
17
|
+
return os.getenv(env_var)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DictionarySource:
|
|
21
|
+
"""A utility for fetching a value from within a JSON-like (nested dict) structure"""
|
|
22
|
+
|
|
23
|
+
# this is quite hacky
|
|
24
|
+
# it only strictly handles two levels of keys in a dict
|
|
25
|
+
@classmethod
|
|
26
|
+
def get(cls, config_key_path: list[str], source_dict: dict) -> str | None:
|
|
27
|
+
"""Gets the value for the specified env var
|
|
28
|
+
|
|
29
|
+
:param config_key_path: the two-level dictionary path to the config key
|
|
30
|
+
:type config_key_path: list[str]
|
|
31
|
+
:param source_dict: JSON-like (nested dict) structure containing config values.
|
|
32
|
+
:type source_dict: dict
|
|
33
|
+
|
|
34
|
+
:return: value for the config key at specified path, or None if not found
|
|
35
|
+
:rtype: str | None
|
|
36
|
+
"""
|
|
37
|
+
assert (
|
|
38
|
+
isinstance(config_key_path, list)
|
|
39
|
+
and len(config_key_path) == 2
|
|
40
|
+
and all([len(path_part) > 0 for path_part in config_key_path])
|
|
41
|
+
)
|
|
42
|
+
return source_dict.get(config_key_path[0], {config_key_path[1]: None}).get(
|
|
43
|
+
config_key_path[1]
|
|
44
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from pyoaev.contracts.contract_config import ContractElement, ContractOutputElement
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContractBuilder:
|
|
7
|
+
fields: List[ContractElement]
|
|
8
|
+
outputs: List[ContractOutputElement]
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.fields = []
|
|
12
|
+
self.outputs = []
|
|
13
|
+
|
|
14
|
+
def add_fields(self, fields: List[ContractElement]):
|
|
15
|
+
self.fields = self.fields + fields
|
|
16
|
+
return self
|
|
17
|
+
|
|
18
|
+
def add_outputs(self, outputs: List[ContractOutputElement]):
|
|
19
|
+
self.outputs = self.outputs + outputs
|
|
20
|
+
return self
|
|
21
|
+
|
|
22
|
+
def mandatory(self, element: ContractElement):
|
|
23
|
+
element.mandatory = True
|
|
24
|
+
self.fields.append(element)
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
def optional(self, element: ContractElement):
|
|
28
|
+
element.mandatory = False
|
|
29
|
+
self.fields.append(element)
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def mandatory_group(self, elements: List[ContractElement]):
|
|
33
|
+
keys: List[str] = list(map(lambda iterable: iterable.key, elements))
|
|
34
|
+
for element in elements:
|
|
35
|
+
element.mandatory = True
|
|
36
|
+
element.mandatoryGroups = keys
|
|
37
|
+
self.fields.append(element)
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def build_fields(self) -> List[ContractElement]:
|
|
41
|
+
return self.fields
|
|
42
|
+
|
|
43
|
+
def build_outputs(self) -> List[ContractOutputElement]:
|
|
44
|
+
return self.outputs
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from pyoaev import utils
|
|
8
|
+
from pyoaev.contracts.contract_utils import ContractCardinality, ContractVariable
|
|
9
|
+
from pyoaev.contracts.variable_helper import VariableHelper
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SupportedLanguage(str, Enum):
|
|
13
|
+
fr: str = "fr"
|
|
14
|
+
en: str = "en"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContractFieldType(str, Enum):
|
|
18
|
+
Text: str = "text"
|
|
19
|
+
Number: str = "number"
|
|
20
|
+
Tuple: str = "tuple"
|
|
21
|
+
Checkbox: str = "checkbox"
|
|
22
|
+
Textarea: str = "textarea"
|
|
23
|
+
Select: str = "select"
|
|
24
|
+
Article: str = "article"
|
|
25
|
+
Challenge: str = "challenge"
|
|
26
|
+
DependencySelect: str = "dependency-select"
|
|
27
|
+
Attachment: str = "attachment"
|
|
28
|
+
Team: str = "team"
|
|
29
|
+
Expectation: str = "expectation"
|
|
30
|
+
Asset: str = "asset"
|
|
31
|
+
AssetGroup: str = "asset-group"
|
|
32
|
+
Payload: str = "payload"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ContractOutputType(str, Enum):
|
|
36
|
+
Text: str = "text"
|
|
37
|
+
Number: str = "number"
|
|
38
|
+
Port: str = "port"
|
|
39
|
+
PortsScan: str = "portscan"
|
|
40
|
+
IPv4: str = "ipv4"
|
|
41
|
+
IPv6: str = "ipv6"
|
|
42
|
+
CVE: str = "cve"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ExpectationType(str, Enum):
|
|
46
|
+
text: str = "TEXT"
|
|
47
|
+
document: str = "DOCUMENT"
|
|
48
|
+
article: str = "ARTICLE"
|
|
49
|
+
challenge: str = "CHALLENGE"
|
|
50
|
+
manual: str = "MANUAL"
|
|
51
|
+
prevention: str = "PREVENTION"
|
|
52
|
+
detection: str = "DETECTION"
|
|
53
|
+
vulnerability: str = "VULNERABILITY"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class Expectation:
|
|
58
|
+
expectation_type: ExpectationType
|
|
59
|
+
expectation_name: str
|
|
60
|
+
expectation_description: str
|
|
61
|
+
expectation_score: int
|
|
62
|
+
expectation_expectation_group: bool
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class LinkedFieldModel:
|
|
67
|
+
key: str
|
|
68
|
+
type: ContractFieldType
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class ContractElement(ABC):
|
|
73
|
+
key: str
|
|
74
|
+
label: str
|
|
75
|
+
type: str = field(default="", init=False)
|
|
76
|
+
mandatoryGroups: List[str] = field(default_factory=list)
|
|
77
|
+
mandatoryConditionFields: List[str] = field(default_factory=list)
|
|
78
|
+
mandatoryConditionValues: Dict[str, any] = field(default_factory=list)
|
|
79
|
+
visibleConditionFields: List[str] = field(default_factory=list)
|
|
80
|
+
visibleConditionValues: Dict[str, any] = field(default_factory=list)
|
|
81
|
+
linkedFields: List[str] = field(default_factory=list)
|
|
82
|
+
mandatory: bool = False
|
|
83
|
+
readOnly: bool = False
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def get_type(self) -> str:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
def __post_init__(self):
|
|
91
|
+
self.type = self.get_type
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class ContractCardinalityElement(ContractElement, ABC):
|
|
96
|
+
cardinality: str = ContractCardinality.One
|
|
97
|
+
defaultValue: List[str] = field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class ContractOutputElement(ABC):
|
|
102
|
+
type: str
|
|
103
|
+
field: str
|
|
104
|
+
labels: List[str]
|
|
105
|
+
isFindingCompatible: bool
|
|
106
|
+
isMultiple: bool
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class ContractConfig:
|
|
111
|
+
type: str
|
|
112
|
+
expose: bool
|
|
113
|
+
label: dict[SupportedLanguage, str]
|
|
114
|
+
color_dark: str
|
|
115
|
+
color_light: str
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass
|
|
119
|
+
class Contract:
|
|
120
|
+
contract_id: str
|
|
121
|
+
label: dict[SupportedLanguage, str]
|
|
122
|
+
fields: List[ContractElement]
|
|
123
|
+
outputs: List[ContractOutputElement]
|
|
124
|
+
config: ContractConfig
|
|
125
|
+
manual: bool
|
|
126
|
+
variables: List[ContractVariable] = field(
|
|
127
|
+
default_factory=lambda: [
|
|
128
|
+
VariableHelper.user_variable(),
|
|
129
|
+
VariableHelper.exercise_variable(),
|
|
130
|
+
VariableHelper.team_variable(),
|
|
131
|
+
]
|
|
132
|
+
+ VariableHelper.uri_variables()
|
|
133
|
+
)
|
|
134
|
+
contract_attack_patterns_external_ids: List[str] = field(default_factory=list)
|
|
135
|
+
contract_vulnerability_external_ids: List[str] = field(default_factory=list)
|
|
136
|
+
is_atomic_testing: bool = True
|
|
137
|
+
platforms: List[str] = field(default_factory=list)
|
|
138
|
+
external_id: str = None
|
|
139
|
+
|
|
140
|
+
def add_attack_pattern(self, var: str):
|
|
141
|
+
self.contract_attack_patterns_external_ids.append(var)
|
|
142
|
+
|
|
143
|
+
def add_vulnerability(self, var: str):
|
|
144
|
+
self.contract_vulnerability_external_ids.append(var)
|
|
145
|
+
|
|
146
|
+
def add_variable(self, var: ContractVariable):
|
|
147
|
+
self.variables.append(var)
|
|
148
|
+
|
|
149
|
+
def to_contract_add_input(self, source_id: str):
|
|
150
|
+
return {
|
|
151
|
+
"contract_id": self.contract_id,
|
|
152
|
+
"external_contract_id": self.external_id,
|
|
153
|
+
"injector_id": source_id,
|
|
154
|
+
"contract_manual": self.manual,
|
|
155
|
+
"contract_labels": self.label,
|
|
156
|
+
"contract_attack_patterns_external_ids": self.contract_attack_patterns_external_ids,
|
|
157
|
+
"contract_vulnerability_external_ids": self.contract_vulnerability_external_ids,
|
|
158
|
+
"contract_content": json.dumps(self, cls=utils.EnhancedJSONEncoder),
|
|
159
|
+
"is_atomic_testing": self.is_atomic_testing,
|
|
160
|
+
"contract_platforms": self.platforms,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
def to_contract_update_input(self):
|
|
164
|
+
return {
|
|
165
|
+
"contract_manual": self.manual,
|
|
166
|
+
"contract_labels": self.label,
|
|
167
|
+
"contract_attack_patterns_external_ids": self.contract_attack_patterns_external_ids,
|
|
168
|
+
"contract_vulnerability_external_ids": self.contract_vulnerability_external_ids,
|
|
169
|
+
"contract_content": json.dumps(self, cls=utils.EnhancedJSONEncoder),
|
|
170
|
+
"is_atomic_testing": self.is_atomic_testing,
|
|
171
|
+
"contract_platforms": self.platforms,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass
|
|
176
|
+
class ContractTeam(ContractCardinalityElement):
|
|
177
|
+
@property
|
|
178
|
+
def get_type(self) -> str:
|
|
179
|
+
return ContractFieldType.Team.value
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass
|
|
183
|
+
class ContractText(ContractCardinalityElement):
|
|
184
|
+
|
|
185
|
+
defaultValue: str = ""
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def get_type(self) -> str:
|
|
189
|
+
return ContractFieldType.Text.value
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def prepare_contracts(contracts):
|
|
193
|
+
return list(
|
|
194
|
+
map(
|
|
195
|
+
lambda c: {
|
|
196
|
+
"contract_id": c.contract_id,
|
|
197
|
+
"contract_labels": c.label,
|
|
198
|
+
"contract_attack_patterns_external_ids": c.contract_attack_patterns_external_ids,
|
|
199
|
+
"contract_content": json.dumps(c, cls=utils.EnhancedJSONEncoder),
|
|
200
|
+
"contract_platforms": c.platforms,
|
|
201
|
+
},
|
|
202
|
+
contracts,
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass
|
|
208
|
+
class ContractTuple(ContractCardinalityElement):
|
|
209
|
+
def __post_init__(self):
|
|
210
|
+
super().__post_init__()
|
|
211
|
+
self.cardinality = ContractCardinality.Multiple
|
|
212
|
+
|
|
213
|
+
attachmentKey: str = None
|
|
214
|
+
contractAttachment: bool = attachmentKey is not None
|
|
215
|
+
tupleFilePrefix: str = "file :: "
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def get_type(self) -> str:
|
|
219
|
+
return ContractFieldType.Tuple.value
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass
|
|
223
|
+
class ContractTextArea(ContractCardinalityElement):
|
|
224
|
+
|
|
225
|
+
defaultValue: str = ""
|
|
226
|
+
richText: bool = False
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def get_type(self) -> str:
|
|
230
|
+
return ContractFieldType.Textarea.value
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class ContractCheckbox(ContractElement):
|
|
235
|
+
|
|
236
|
+
defaultValue: bool = False
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def get_type(self) -> str:
|
|
240
|
+
return ContractFieldType.Checkbox.value
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@dataclass
|
|
244
|
+
class ContractAttachment(ContractCardinalityElement):
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def get_type(self) -> str:
|
|
248
|
+
return ContractFieldType.Attachment.value
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass
|
|
252
|
+
class ContractExpectations(ContractCardinalityElement):
|
|
253
|
+
cardinality = ContractCardinality.Multiple
|
|
254
|
+
predefinedExpectations: List[Expectation] = field(default_factory=list)
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def get_type(self) -> str:
|
|
258
|
+
return ContractFieldType.Expectation.value
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@dataclass
|
|
262
|
+
class ContractSelect(ContractCardinalityElement):
|
|
263
|
+
|
|
264
|
+
choices: dict[str, str] = None
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def get_type(self) -> str:
|
|
268
|
+
return ContractFieldType.Select.value
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@dataclass
|
|
272
|
+
class ContractAsset(ContractCardinalityElement):
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def get_type(self) -> str:
|
|
276
|
+
return ContractFieldType.Asset.value
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@dataclass
|
|
280
|
+
class ContractAssetGroup(ContractCardinalityElement):
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def get_type(self) -> str:
|
|
284
|
+
return ContractFieldType.AssetGroup.value
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@dataclass
|
|
288
|
+
class ContractPayload(ContractCardinalityElement):
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def get_type(self) -> str:
|
|
292
|
+
return ContractFieldType.Payload.value
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContractCardinality(str, Enum):
|
|
7
|
+
One: str = "1"
|
|
8
|
+
Multiple: str = "n"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VariableType(str, Enum):
|
|
12
|
+
String: str = "String"
|
|
13
|
+
Object: str = "Object"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ContractVariable:
|
|
18
|
+
key: str
|
|
19
|
+
label: str
|
|
20
|
+
type: VariableType
|
|
21
|
+
cardinality: ContractCardinality
|
|
22
|
+
children: List["ContractVariable"] = field(default_factory=list)
|