mal-toolbox 0.2.0__py3-none-any.whl → 0.3.1__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.
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.1.dist-info}/METADATA +43 -25
- mal_toolbox-0.3.1.dist-info/RECORD +29 -0
- mal_toolbox-0.3.1.dist-info/entry_points.txt +2 -0
- maltoolbox/__init__.py +38 -57
- maltoolbox/__main__.py +43 -14
- maltoolbox/attackgraph/__init__.py +1 -1
- maltoolbox/attackgraph/analyzers/apriori.py +6 -5
- maltoolbox/attackgraph/attacker.py +26 -13
- maltoolbox/attackgraph/attackgraph.py +185 -153
- maltoolbox/attackgraph/node.py +56 -54
- maltoolbox/attackgraph/query.py +4 -2
- maltoolbox/file_utils.py +0 -8
- maltoolbox/ingestors/neo4j.py +146 -157
- maltoolbox/language/__init__.py +7 -3
- maltoolbox/language/compiler/__init__.py +485 -17
- maltoolbox/language/compiler/mal_lexer.py +172 -152
- maltoolbox/language/compiler/mal_parser.py +1370 -663
- maltoolbox/language/languagegraph.py +103 -99
- maltoolbox/model.py +306 -488
- maltoolbox/translators/securicad.py +164 -163
- maltoolbox/translators/updater.py +231 -108
- mal_toolbox-0.2.0.dist-info/RECORD +0 -32
- maltoolbox/default.conf +0 -17
- maltoolbox/language/classes_factory.py +0 -259
- maltoolbox/language/compiler/mal_visitor.py +0 -416
- maltoolbox/wrappers.py +0 -62
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.1.dist-info}/AUTHORS +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.1.dist-info}/LICENSE +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.1.dist-info}/WHEEL +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.1.dist-info}/top_level.txt +0 -0
maltoolbox/attackgraph/node.py
CHANGED
|
@@ -1,59 +1,68 @@
|
|
|
1
1
|
"""
|
|
2
|
-
MAL-Toolbox Attack Graph Node
|
|
2
|
+
MAL-Toolbox Attack Graph Node
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
import copy
|
|
7
|
-
from dataclasses import field, dataclass
|
|
8
7
|
from functools import cached_property
|
|
9
|
-
from typing import
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
from
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
from . import Attacker
|
|
13
|
+
from ..language import LanguageGraphAttackStep, Detector
|
|
14
|
+
from ..model import ModelAsset
|
|
13
15
|
|
|
14
|
-
@dataclass
|
|
15
16
|
class AttackGraphNode:
|
|
16
17
|
"""Node part of AttackGraph"""
|
|
17
|
-
type: str
|
|
18
|
-
lang_graph_attack_step: LanguageGraphAttackStep
|
|
19
|
-
name: str
|
|
20
|
-
ttc: Optional[dict] = None
|
|
21
|
-
id: Optional[int] = None
|
|
22
|
-
asset: Optional[Any] = None
|
|
23
|
-
children: list[AttackGraphNode] = field(default_factory=list)
|
|
24
|
-
parents: list[AttackGraphNode] = field(default_factory=list)
|
|
25
|
-
defense_status: Optional[float] = None
|
|
26
|
-
existence_status: Optional[bool] = None
|
|
27
|
-
is_viable: bool = True
|
|
28
|
-
is_necessary: bool = True
|
|
29
|
-
compromised_by: list[Attacker] = field(default_factory=list)
|
|
30
|
-
tags: set[str] = field(default_factory=set)
|
|
31
|
-
attributes: Optional[dict] = None
|
|
32
|
-
|
|
33
|
-
# Optional extra metadata for AttackGraphNode
|
|
34
|
-
extras: dict = field(default_factory=dict)
|
|
35
18
|
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
node_id: int,
|
|
22
|
+
lg_attack_step: LanguageGraphAttackStep,
|
|
23
|
+
model_asset: Optional[ModelAsset] = None,
|
|
24
|
+
defense_status: Optional[float] = None,
|
|
25
|
+
existence_status: Optional[bool] = None
|
|
26
|
+
):
|
|
27
|
+
self.lg_attack_step = lg_attack_step
|
|
28
|
+
self.name = lg_attack_step.name
|
|
29
|
+
self.type = lg_attack_step.type
|
|
30
|
+
self.ttc = lg_attack_step.ttc
|
|
31
|
+
self.tags = lg_attack_step.tags
|
|
32
|
+
self.detectors = lg_attack_step.detectors
|
|
33
|
+
|
|
34
|
+
self.id = node_id
|
|
35
|
+
self.model_asset = model_asset
|
|
36
|
+
self.defense_status = defense_status
|
|
37
|
+
self.existence_status = existence_status
|
|
38
|
+
|
|
39
|
+
self.children: set[AttackGraphNode] = set()
|
|
40
|
+
self.parents: set[AttackGraphNode] = set()
|
|
41
|
+
self.is_viable: bool = True
|
|
42
|
+
self.is_necessary: bool = True
|
|
43
|
+
self.compromised_by: set[Attacker] = set()
|
|
44
|
+
self.extras: dict = {}
|
|
36
45
|
|
|
37
46
|
def to_dict(self) -> dict:
|
|
38
47
|
"""Convert node to dictionary"""
|
|
39
48
|
node_dict: dict = {
|
|
40
49
|
'id': self.id,
|
|
41
50
|
'type': self.type,
|
|
42
|
-
'lang_graph_attack_step': self.
|
|
51
|
+
'lang_graph_attack_step': self.lg_attack_step.full_name,
|
|
43
52
|
'name': self.name,
|
|
44
53
|
'ttc': self.ttc,
|
|
45
|
-
'children': {
|
|
46
|
-
|
|
54
|
+
'children': {child.id: child.full_name for child in
|
|
55
|
+
self.children},
|
|
56
|
+
'parents': {parent.id: parent.full_name for parent in
|
|
57
|
+
self.parents},
|
|
47
58
|
'compromised_by': [attacker.name for attacker in \
|
|
48
59
|
self.compromised_by]
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
for
|
|
52
|
-
node_dict
|
|
53
|
-
|
|
54
|
-
node_dict['
|
|
55
|
-
if self.asset is not None:
|
|
56
|
-
node_dict['asset'] = str(self.asset.name)
|
|
62
|
+
for detector in self.detectors.values():
|
|
63
|
+
node_dict.setdefault('detectors', {})[detector.name] = detector.to_dict()
|
|
64
|
+
if self.model_asset is not None:
|
|
65
|
+
node_dict['asset'] = str(self.model_asset.name)
|
|
57
66
|
if self.defense_status is not None:
|
|
58
67
|
node_dict['defense_status'] = str(self.defense_status)
|
|
59
68
|
if self.existence_status is not None:
|
|
@@ -71,7 +80,8 @@ class AttackGraphNode:
|
|
|
71
80
|
|
|
72
81
|
|
|
73
82
|
def __repr__(self) -> str:
|
|
74
|
-
return
|
|
83
|
+
return (f'AttackGraphNode(name: "{self.full_name}", id: {self.id}, '
|
|
84
|
+
f'type: {self.type})')
|
|
75
85
|
|
|
76
86
|
|
|
77
87
|
def __deepcopy__(self, memo) -> AttackGraphNode:
|
|
@@ -89,27 +99,19 @@ class AttackGraphNode:
|
|
|
89
99
|
return memo[id(self)]
|
|
90
100
|
|
|
91
101
|
copied_node = AttackGraphNode(
|
|
92
|
-
self.
|
|
93
|
-
self.
|
|
94
|
-
self.
|
|
95
|
-
self.ttc,
|
|
96
|
-
self.id,
|
|
97
|
-
self.asset,
|
|
98
|
-
[],
|
|
99
|
-
[],
|
|
100
|
-
self.defense_status,
|
|
101
|
-
self.existence_status,
|
|
102
|
-
self.is_viable,
|
|
103
|
-
self.is_necessary,
|
|
104
|
-
[],
|
|
105
|
-
set(),
|
|
106
|
-
{},
|
|
107
|
-
{}
|
|
102
|
+
node_id = self.id,
|
|
103
|
+
model_asset = self.model_asset,
|
|
104
|
+
lg_attack_step = self.lg_attack_step
|
|
108
105
|
)
|
|
109
106
|
|
|
110
107
|
copied_node.tags = copy.deepcopy(self.tags, memo)
|
|
111
|
-
copied_node.attributes = copy.deepcopy(self.attributes, memo)
|
|
112
108
|
copied_node.extras = copy.deepcopy(self.extras, memo)
|
|
109
|
+
copied_node.ttc = copy.deepcopy(self.ttc, memo)
|
|
110
|
+
|
|
111
|
+
copied_node.defense_status = self.defense_status
|
|
112
|
+
copied_node.existence_status = self.existence_status
|
|
113
|
+
copied_node.is_viable = self.is_viable
|
|
114
|
+
copied_node.is_necessary = self.is_necessary
|
|
113
115
|
|
|
114
116
|
# Remember that self was already copied
|
|
115
117
|
memo[id(self)] = copied_node
|
|
@@ -187,8 +189,8 @@ class AttackGraphNode:
|
|
|
187
189
|
asset name to which the attack step belongs and attack step name
|
|
188
190
|
itself.
|
|
189
191
|
"""
|
|
190
|
-
if self.
|
|
191
|
-
full_name = self.
|
|
192
|
+
if self.model_asset:
|
|
193
|
+
full_name = self.model_asset.name + ':' + self.name
|
|
192
194
|
else:
|
|
193
195
|
full_name = str(self.id) + ':' + self.name
|
|
194
196
|
return full_name
|
|
@@ -196,4 +198,4 @@ class AttackGraphNode:
|
|
|
196
198
|
|
|
197
199
|
@cached_property
|
|
198
200
|
def info(self) -> dict[str, str]:
|
|
199
|
-
return self.
|
|
201
|
+
return self.lg_attack_step.info
|
maltoolbox/attackgraph/query.py
CHANGED
|
@@ -186,7 +186,8 @@ def get_defense_surface(graph: AttackGraph) -> list[AttackGraphNode]:
|
|
|
186
186
|
graph - the attack graph
|
|
187
187
|
"""
|
|
188
188
|
logger.debug('Get the defense surface.')
|
|
189
|
-
return [node for node in graph.nodes
|
|
189
|
+
return [node for node in graph.nodes.values()
|
|
190
|
+
if node.is_available_defense()]
|
|
190
191
|
|
|
191
192
|
def get_enabled_defenses(graph: AttackGraph) -> list[AttackGraphNode]:
|
|
192
193
|
"""
|
|
@@ -197,4 +198,5 @@ def get_enabled_defenses(graph: AttackGraph) -> list[AttackGraphNode]:
|
|
|
197
198
|
graph - the attack graph
|
|
198
199
|
"""
|
|
199
200
|
logger.debug('Get the enabled defenses.')
|
|
200
|
-
return [node for node in graph.nodes
|
|
201
|
+
return [node for node in graph.nodes.values()
|
|
202
|
+
if node.is_enabled_defense()]
|
maltoolbox/file_utils.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import yaml
|
|
5
|
-
from python_jsonschema_objects.literals import LiteralValue
|
|
6
5
|
|
|
7
6
|
def save_dict_to_json_file(filename: str, serialized_object: dict) -> None:
|
|
8
7
|
"""Save serialized object to a json file.
|
|
@@ -28,13 +27,6 @@ def save_dict_to_yaml_file(filename: str, serialized_object: dict) -> None:
|
|
|
28
27
|
def ignore_aliases(self, data):
|
|
29
28
|
return True
|
|
30
29
|
|
|
31
|
-
# Handle Literal values from jsonschema_objects
|
|
32
|
-
yaml.add_multi_representer(
|
|
33
|
-
LiteralValue,
|
|
34
|
-
lambda dumper, data: dumper.represent_data(data._value),
|
|
35
|
-
NoAliasSafeDumper
|
|
36
|
-
)
|
|
37
|
-
|
|
38
30
|
with open(filename, 'w', encoding='utf-8') as f:
|
|
39
31
|
yaml.dump(serialized_object, f, Dumper=NoAliasSafeDumper)
|
|
40
32
|
|
maltoolbox/ingestors/neo4j.py
CHANGED
|
@@ -7,9 +7,6 @@ import logging
|
|
|
7
7
|
|
|
8
8
|
from py2neo import Graph, Node, Relationship, Subgraph
|
|
9
9
|
|
|
10
|
-
from ..model import AttackerAttachment, Model
|
|
11
|
-
from ..language import LanguageGraph, LanguageClassesFactory
|
|
12
|
-
|
|
13
10
|
logger = logging.getLogger(__name__)
|
|
14
11
|
|
|
15
12
|
def ingest_attack_graph(graph,
|
|
@@ -90,26 +87,24 @@ def ingest_model(model,
|
|
|
90
87
|
nodes = {}
|
|
91
88
|
rels = []
|
|
92
89
|
|
|
93
|
-
for asset in model.assets:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for assoc in model.associations:
|
|
101
|
-
firstElementName, secondElementName = assoc._properties.keys()
|
|
102
|
-
firstElements = getattr(assoc, firstElementName)
|
|
103
|
-
secondElements = getattr(assoc, secondElementName)
|
|
104
|
-
for first_asset in firstElements:
|
|
105
|
-
for second_asset in secondElements:
|
|
106
|
-
rels.append(Relationship(nodes[str(first_asset.id)],
|
|
107
|
-
str(firstElementName),
|
|
108
|
-
nodes[str(second_asset.id)]))
|
|
109
|
-
rels.append(Relationship(nodes[str(second_asset.id)],
|
|
110
|
-
str(secondElementName),
|
|
111
|
-
nodes[str(first_asset.id)]))
|
|
90
|
+
for asset in model.assets.values():
|
|
91
|
+
nodes[str(asset.id)] = Node(
|
|
92
|
+
str(asset.type),
|
|
93
|
+
name=str(asset.name),
|
|
94
|
+
asset_id=str(asset.id),
|
|
95
|
+
type=str(asset.type)
|
|
96
|
+
)
|
|
112
97
|
|
|
98
|
+
for asset in model.assets.values():
|
|
99
|
+
for fieldname, other_assets in asset.associated_assets.items():
|
|
100
|
+
for other_asset in other_assets:
|
|
101
|
+
rels.append(
|
|
102
|
+
Relationship(
|
|
103
|
+
nodes[str(asset.id)],
|
|
104
|
+
str(fieldname),
|
|
105
|
+
nodes[str(other_asset.id)]
|
|
106
|
+
)
|
|
107
|
+
)
|
|
113
108
|
|
|
114
109
|
subgraph = Subgraph(list(nodes.values()), rels)
|
|
115
110
|
|
|
@@ -118,138 +113,132 @@ def ingest_model(model,
|
|
|
118
113
|
g.commit(tx)
|
|
119
114
|
|
|
120
115
|
|
|
121
|
-
def get_model(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
right_asset,
|
|
251
|
-
left_asset
|
|
252
|
-
)):
|
|
253
|
-
instance_model.add_association(assoc)
|
|
254
|
-
|
|
255
|
-
return instance_model
|
|
116
|
+
# def get_model(
|
|
117
|
+
# uri: str,
|
|
118
|
+
# username: str,
|
|
119
|
+
# password: str,
|
|
120
|
+
# dbname: str,
|
|
121
|
+
# lang_graph: LanguageGraph,
|
|
122
|
+
# ) -> Model:
|
|
123
|
+
# """Load a model from Neo4j"""
|
|
124
|
+
|
|
125
|
+
# g = Graph(uri=uri, user=username, password=password, name=dbname)
|
|
126
|
+
|
|
127
|
+
# instance_model = Model('Neo4j imported model', lang_graph)
|
|
128
|
+
# # Get all assets
|
|
129
|
+
# assets_results = g.run('MATCH (a) WHERE a.type IS NOT NULL RETURN DISTINCT a').data()
|
|
130
|
+
# for asset in assets_results:
|
|
131
|
+
# asset_data = dict(asset['a'])
|
|
132
|
+
# logger.debug(
|
|
133
|
+
# 'Loading asset from Neo4j instance:\n%s', str(asset_data)
|
|
134
|
+
# )
|
|
135
|
+
# if asset_data['type'] == 'Attacker':
|
|
136
|
+
# attacker_id = int(asset_data['asset_id'])
|
|
137
|
+
# attacker = AttackerAttachment()
|
|
138
|
+
# attacker.entry_points = []
|
|
139
|
+
# instance_model.add_attacker(attacker, attacker_id = attacker_id)
|
|
140
|
+
# continue
|
|
141
|
+
|
|
142
|
+
# asset_id = int(asset_data['asset_id'])
|
|
143
|
+
|
|
144
|
+
# #TODO Process defense values when they are included in Neo4j
|
|
145
|
+
# instance_model.add_asset(asset_data['type'], asset_id=asset_id)
|
|
146
|
+
|
|
147
|
+
# # Get all relationships
|
|
148
|
+
# assocs_results = g.run(
|
|
149
|
+
# 'MATCH (a)-[r1]->(b),(a)<-[r2]-(b) WHERE a.type IS NOT NULL RETURN DISTINCT a, r1, r2, b'
|
|
150
|
+
# ).data()
|
|
151
|
+
|
|
152
|
+
# for assoc in assocs_results:
|
|
153
|
+
# left_field = list(assoc['r1'].types())[0]
|
|
154
|
+
# right_field = list(assoc['r2'].types())[0]
|
|
155
|
+
# left_asset = dict(assoc['a'])
|
|
156
|
+
# right_asset = dict(assoc['b'])
|
|
157
|
+
|
|
158
|
+
# logger.debug(
|
|
159
|
+
# 'Load association ("%s", "%s", "%s", "%s") from Neo4j instance.',
|
|
160
|
+
# left_field, right_field, left_asset["type"], right_asset["type"]
|
|
161
|
+
# )
|
|
162
|
+
|
|
163
|
+
# left_id = int(left_asset['asset_id'])
|
|
164
|
+
# right_id = int(right_asset['asset_id'])
|
|
165
|
+
|
|
166
|
+
# attacker_id = None
|
|
167
|
+
# if left_field == 'firstSteps':
|
|
168
|
+
# attacker_id = right_id
|
|
169
|
+
# target_id = left_id
|
|
170
|
+
# target_prop = right_field
|
|
171
|
+
# elif right_field == 'firstSteps':
|
|
172
|
+
# attacker_id = left_id
|
|
173
|
+
# target_id = right_id
|
|
174
|
+
# target_prop = left_field
|
|
175
|
+
|
|
176
|
+
# if attacker_id is not None:
|
|
177
|
+
# attacker = instance_model.get_attacker_by_id(attacker_id)
|
|
178
|
+
# if not attacker:
|
|
179
|
+
# msg = 'Failed to find attacker with id %s in model!'
|
|
180
|
+
# logger.error(msg, attacker_id)
|
|
181
|
+
# raise LookupError(msg % attacker_id)
|
|
182
|
+
# target_asset = instance_model.get_asset_by_id(target_id)
|
|
183
|
+
# if not target_asset:
|
|
184
|
+
# msg = 'Failed to find asset with id %d in model!'
|
|
185
|
+
# logger.error(msg, target_id)
|
|
186
|
+
# raise LookupError(msg % target_id)
|
|
187
|
+
# attacker.entry_points.append((target_asset,
|
|
188
|
+
# [target_prop]))
|
|
189
|
+
# continue
|
|
190
|
+
|
|
191
|
+
# left_asset = instance_model.get_asset_by_id(left_id)
|
|
192
|
+
# if left_asset is None:
|
|
193
|
+
# msg = 'Failed to find asset with id %d in model!'
|
|
194
|
+
# logger.error(msg, left_id)
|
|
195
|
+
# raise LookupError(msg % left_id)
|
|
196
|
+
# right_asset = instance_model.get_asset_by_id(right_id)
|
|
197
|
+
# if right_asset is None:
|
|
198
|
+
# msg = 'Failed to find asset with id %d in model!'
|
|
199
|
+
# logger.error(msg, right_id)
|
|
200
|
+
# raise LookupError(msg % right_id)
|
|
201
|
+
|
|
202
|
+
# assoc = lang_graph.get_association_by_fields_and_assets(
|
|
203
|
+
# left_field,
|
|
204
|
+
# right_field,
|
|
205
|
+
# left_asset.type,
|
|
206
|
+
# right_asset.type)
|
|
207
|
+
|
|
208
|
+
# if not assoc:
|
|
209
|
+
# logger.error(
|
|
210
|
+
# 'Failed to find ("%s", "%s", "%s", "%s")'
|
|
211
|
+
# 'association in language specification!',
|
|
212
|
+
# left_asset.type, right_asset.type,
|
|
213
|
+
# left_field, right_field
|
|
214
|
+
# )
|
|
215
|
+
# return None
|
|
216
|
+
|
|
217
|
+
# logger.debug('Found "%s" association.', assoc.name)
|
|
218
|
+
|
|
219
|
+
# assoc_name = lang_classes_factory.get_association_by_signature(
|
|
220
|
+
# assoc.name,
|
|
221
|
+
# left_asset.type,
|
|
222
|
+
# right_asset.type
|
|
223
|
+
# )
|
|
224
|
+
|
|
225
|
+
# if not assoc_name:
|
|
226
|
+
# msg = 'Failed to find \"%s\" association in language specification!'
|
|
227
|
+
# logger.error(msg, assoc.name)
|
|
228
|
+
# raise LookupError(msg % assoc.name)
|
|
229
|
+
|
|
230
|
+
# assoc = getattr(lang_classes_factory.ns, assoc_name)()
|
|
231
|
+
# setattr(assoc, left_field, [left_asset])
|
|
232
|
+
# setattr(assoc, right_field, [right_asset])
|
|
233
|
+
# if not (instance_model.association_exists_between_assets(
|
|
234
|
+
# assoc_name,
|
|
235
|
+
# left_asset,
|
|
236
|
+
# right_asset
|
|
237
|
+
# ) or instance_model.association_exists_between_assets(
|
|
238
|
+
# assoc_name,
|
|
239
|
+
# right_asset,
|
|
240
|
+
# left_asset
|
|
241
|
+
# )):
|
|
242
|
+
# instance_model.add_association(assoc)
|
|
243
|
+
|
|
244
|
+
# return instance_model
|
maltoolbox/language/__init__.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"""Contains tools to process MAL languages"""
|
|
2
2
|
|
|
3
|
-
from .languagegraph import (
|
|
3
|
+
from .languagegraph import (
|
|
4
|
+
Context,
|
|
5
|
+
Detector,
|
|
4
6
|
ExpressionsChain,
|
|
7
|
+
LanguageGraph,
|
|
5
8
|
LanguageGraphAsset,
|
|
9
|
+
LanguageGraphAssociation,
|
|
6
10
|
LanguageGraphAttackStep,
|
|
7
|
-
disaggregate_attack_step_full_name
|
|
8
|
-
|
|
11
|
+
disaggregate_attack_step_full_name,
|
|
12
|
+
)
|