mal-toolbox 0.1.7__tar.gz → 0.1.9__tar.gz

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.
Files changed (40) hide show
  1. {mal_toolbox-0.1.7/mal_toolbox.egg-info → mal_toolbox-0.1.9}/PKG-INFO +7 -1
  2. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/README.md +6 -0
  3. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9/mal_toolbox.egg-info}/PKG-INFO +7 -1
  4. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/__init__.py +2 -2
  5. mal_toolbox-0.1.9/maltoolbox/attackgraph/__init__.py +8 -0
  6. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/attackgraph/attackgraph.py +26 -1
  7. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/attackgraph/node.py +34 -0
  8. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/attackgraph/query.py +43 -2
  9. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/language/__init__.py +2 -0
  10. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/language/classes_factory.py +3 -2
  11. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/model.py +52 -23
  12. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/pyproject.toml +2 -3
  13. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/tests/test_model.py +77 -21
  14. mal_toolbox-0.1.7/maltoolbox/attackgraph/__init__.py +0 -3
  15. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/AUTHORS +0 -0
  16. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/LICENSE +0 -0
  17. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/mal_toolbox.egg-info/SOURCES.txt +0 -0
  18. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/mal_toolbox.egg-info/dependency_links.txt +0 -0
  19. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/mal_toolbox.egg-info/requires.txt +0 -0
  20. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/mal_toolbox.egg-info/top_level.txt +0 -0
  21. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/__main__.py +0 -0
  22. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
  23. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/attackgraph/analyzers/apriori.py +0 -0
  24. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/attackgraph/attacker.py +0 -0
  25. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/default.conf +0 -0
  26. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/exceptions.py +0 -0
  27. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/file_utils.py +0 -0
  28. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/ingestors/__init__.py +0 -0
  29. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/ingestors/neo4j.py +0 -0
  30. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/language/compiler/__init__.py +0 -0
  31. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/language/compiler/mal_lexer.py +0 -0
  32. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/language/compiler/mal_parser.py +0 -0
  33. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/language/compiler/mal_visitor.py +0 -0
  34. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/language/languagegraph.py +0 -0
  35. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/translators/__init__.py +0 -0
  36. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/translators/securicad.py +0 -0
  37. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/translators/updater.py +0 -0
  38. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/maltoolbox/wrappers.py +0 -0
  39. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/setup.cfg +0 -0
  40. {mal_toolbox-0.1.7 → mal_toolbox-0.1.9}/tests/test_wrappers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mal-toolbox
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: A collection of tools used to create MAL models and attack graphs.
5
5
  Author-email: Andrei Buhaiu <buhaiu@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Joakim Loxdal <loxdal@kth.se>
6
6
  License: Apache Software License
@@ -102,6 +102,12 @@ and it can be used to visualise the instance model and the attack graph.
102
102
 
103
103
  # Usage
104
104
 
105
+ ## Installation
106
+
107
+ ```
108
+ pip install mal-toolbox
109
+ ```
110
+
105
111
  ## Configuration
106
112
  A default configuration file `default.conf` can be found in the package
107
113
  directory. This contains the default values to use for logging and can also be
@@ -75,6 +75,12 @@ and it can be used to visualise the instance model and the attack graph.
75
75
 
76
76
  # Usage
77
77
 
78
+ ## Installation
79
+
80
+ ```
81
+ pip install mal-toolbox
82
+ ```
83
+
78
84
  ## Configuration
79
85
  A default configuration file `default.conf` can be found in the package
80
86
  directory. This contains the default values to use for logging and can also be
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mal-toolbox
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: A collection of tools used to create MAL models and attack graphs.
5
5
  Author-email: Andrei Buhaiu <buhaiu@kth.se>, Giuseppe Nebbione <nebbione@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Joakim Loxdal <loxdal@kth.se>
6
6
  License: Apache Software License
@@ -102,6 +102,12 @@ and it can be used to visualise the instance model and the attack graph.
102
102
 
103
103
  # Usage
104
104
 
105
+ ## Installation
106
+
107
+ ```
108
+ pip install mal-toolbox
109
+ ```
110
+
105
111
  ## Configuration
106
112
  A default configuration file `default.conf` can be found in the package
107
113
  directory. This contains the default values to use for logging and can also be
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # MAL Toolbox v0.1.7
2
+ # MAL Toolbox v0.1.9
3
3
  # Copyright 2024, Andrei Buhaiu.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,7 @@ MAL-Toolbox Framework
21
21
  """
22
22
 
23
23
  __title__ = 'maltoolbox'
24
- __version__ = '0.1.7'
24
+ __version__ = '0.1.9'
25
25
  __authors__ = ['Andrei Buhaiu',
26
26
  'Giuseppe Nebbione',
27
27
  'Nikolaos Kakouros',
@@ -0,0 +1,8 @@
1
+ """
2
+ Contains tools used to generate attack graphs from MAL instance
3
+ models and analyze attack graphs.
4
+ """
5
+
6
+ from .attacker import Attacker
7
+ from .attackgraph import AttackGraph
8
+ from .node import AttackGraphNode
@@ -2,6 +2,7 @@
2
2
  MAL-Toolbox Attack Graph Module
3
3
  """
4
4
  from __future__ import annotations
5
+ import copy
5
6
  import logging
6
7
  import json
7
8
 
@@ -224,6 +225,30 @@ class AttackGraph():
224
225
  'attackers': serialized_attackers,
225
226
  }
226
227
 
228
+ def __deepcopy__(self, memo):
229
+ copied_attackgraph = AttackGraph(self.lang_graph)
230
+ copied_attackgraph.model = self.model
231
+
232
+ # Deep copy nodes and add references to them
233
+ copied_attackgraph.nodes = copy.deepcopy(self.nodes, memo)
234
+
235
+ # Deep copy attackers and references to them
236
+ copied_attackgraph.attackers = copy.deepcopy(self.attackers, memo)
237
+
238
+ # Copy lookup dicts
239
+ copied_attackgraph._id_to_attacker = \
240
+ copy.deepcopy(self._id_to_attacker, memo)
241
+ copied_attackgraph._id_to_node = \
242
+ copy.deepcopy(self._id_to_node, memo)
243
+ copied_attackgraph._full_name_to_node = \
244
+ copy.deepcopy(self._full_name_to_node, memo)
245
+
246
+ # Copy counters
247
+ copied_attackgraph.next_node_id = self.next_node_id
248
+ copied_attackgraph.next_attacker_id = self.next_attacker_id
249
+
250
+ return copied_attackgraph
251
+
227
252
  def save_to_file(self, filename: str) -> None:
228
253
  """Save to json/yml depending on extension"""
229
254
  logger.debug('Save attack graph to file "%s".', filename)
@@ -382,7 +407,7 @@ class AttackGraph():
382
407
  The attack step node that matches the given full name.
383
408
  """
384
409
 
385
- logger.debug(f'Looking up node with id {full_name}')
410
+ logger.debug(f'Looking up node with full name "{full_name}"')
386
411
  return self._full_name_to_node.get(full_name)
387
412
 
388
413
  def get_attacker_by_id(self, attacker_id: int) -> Optional[Attacker]:
@@ -3,6 +3,7 @@ MAL-Toolbox Attack Graph Node Dataclass
3
3
  """
4
4
 
5
5
  from __future__ import annotations
6
+ import copy
6
7
  from dataclasses import field, dataclass
7
8
  from typing import Any, Optional
8
9
 
@@ -69,6 +70,39 @@ class AttackGraphNode:
69
70
  def __repr__(self) -> str:
70
71
  return str(self.to_dict())
71
72
 
73
+ def __deepcopy__(self, memo) -> AttackGraphNode:
74
+ """Deep copy an attackgraph node"""
75
+
76
+ copied_node = AttackGraphNode(
77
+ self.type,
78
+ self.name,
79
+ self.ttc,
80
+ self.id,
81
+ self.asset,
82
+ [],
83
+ [],
84
+ self.defense_status,
85
+ self.existence_status,
86
+ self.is_viable,
87
+ self.is_necessary,
88
+ copy.deepcopy(self.compromised_by, memo),
89
+ self.mitre_info,
90
+ copy.deepcopy(self.tags, memo),
91
+ copy.deepcopy(self.attributes, memo),
92
+ copy.deepcopy(self.extras, memo)
93
+ )
94
+
95
+ # Remember that self was already copied
96
+ memo[id(self)] = copied_node
97
+
98
+ # Deep copy children and parents, send memo (avoid infinite recursion)
99
+ if self.parents:
100
+ copied_node.parents = copy.deepcopy(self.parents, memo)
101
+ if self.children:
102
+ copied_node.children = copy.deepcopy(self.children, memo)
103
+
104
+ return copied_node
105
+
72
106
  def is_compromised(self) -> bool:
73
107
  """
74
108
  Return True if any attackers have compromised this node.
@@ -37,10 +37,21 @@ def is_node_traversable_by_attacker(
37
37
  attacker.id
38
38
  )
39
39
  if not node.is_viable:
40
+ logger.debug(
41
+ '"%s"(%d) is not traversable because it is non-viable',
42
+ node.full_name,
43
+ node.id,
44
+ )
40
45
  return False
41
46
 
42
47
  match(node.type):
43
48
  case 'or':
49
+ logger.debug(
50
+ '"%s"(%d) is traversable because it is viable and '
51
+ 'of type "or".',
52
+ node.full_name,
53
+ node.id
54
+ )
44
55
  return True
45
56
 
46
57
  case 'and':
@@ -49,14 +60,44 @@ def is_node_traversable_by_attacker(
49
60
  not parent.is_compromised_by(attacker):
50
61
  # If the parent is not present in the attacks steps
51
62
  # already reached and is necessary.
63
+ logger.debug(
64
+ '"%s"(%d) is not traversable because while it is '
65
+ 'viable, and of type "and", its necessary parent '
66
+ '"%s(%d)" has not already been compromised.',
67
+ node.full_name,
68
+ node.id,
69
+ parent.full_name,
70
+ parent.id
71
+ )
52
72
  return False
73
+ logger.debug(
74
+ '"%s"(%d) is traversable because it is viable, '
75
+ 'of type "and", and all of its necessary parents have '
76
+ 'already been compromised.',
77
+ node.full_name,
78
+ node.id
79
+ )
53
80
  return True
54
81
 
55
- case 'exist' | 'notExist' | 'defense':
82
+ case 'exist' | 'notExist' | 'defense':
83
+ logger.warning(
84
+ 'Nodes of type "exist", "notExist", and "defense" are never '
85
+ 'marked as traversable. However, we do not normally check '
86
+ 'if they are traversable. Node "%s"(%d) of type "%s" was '
87
+ 'checked for traversability.',
88
+ node.full_name,
89
+ node.id,
90
+ node.type
91
+ )
56
92
  return False
57
93
 
58
94
  case _:
59
- logger.error('Unknown node type %s.', node.type)
95
+ logger.error(
96
+ 'Node "%s"(%d) has an unknown type "%s".',
97
+ node.full_name,
98
+ node.id,
99
+ node.type
100
+ )
60
101
  return False
61
102
 
62
103
  def get_attack_surface(
@@ -1,2 +1,4 @@
1
+ """Contains tools to process MAL languages"""
2
+
1
3
  from .languagegraph import LanguageGraph
2
4
  from .classes_factory import LanguageClassesFactory
@@ -1,5 +1,6 @@
1
1
  """
2
2
  MAL-Toolbox Language Classes Factory Module
3
+ Uses python_jsonschema_objects to generate python classes from a MAL language
3
4
  """
4
5
  from __future__ import annotations
5
6
  import json
@@ -25,7 +26,7 @@ class LanguageClassesFactory:
25
26
 
26
27
  def _generate_assets(self) -> None:
27
28
  """
28
- Generate JSON Schema for the assets in the language specification.
29
+ Generate JSON Schema for asset types in the language specification.
29
30
  """
30
31
  for asset in self.lang_graph.assets:
31
32
  logger.debug('Creating %s asset JSON schema entry.', asset.name)
@@ -67,7 +68,7 @@ class LanguageClassesFactory:
67
68
 
68
69
  def _generate_associations(self) -> None:
69
70
  """
70
- Generate JSON Schema for the associations in the language specification.
71
+ Generate JSON Schema for association types in the language specification.
71
72
  """
72
73
  def create_association_entry(assoc: SchemaGeneratedClass):
73
74
  logger.debug('Creating %s association JSON schema entry.', assoc.name)
@@ -28,24 +28,45 @@ logger = logging.getLogger(__name__)
28
28
 
29
29
  @dataclass
30
30
  class AttackerAttachment:
31
- """Used to attach attackers to attack step entrypoints of assets"""
31
+ """Used to attach attackers to attack step entry points of assets"""
32
32
  id: Optional[int] = None
33
33
  name: Optional[str] = None
34
34
  entry_points: list[tuple[SchemaGeneratedClass, list[str]]] = \
35
35
  field(default_factory=lambda: [])
36
36
 
37
37
 
38
- def add_entrypoint(
38
+ def get_entry_point_tuple(
39
+ self,
40
+ asset: SchemaGeneratedClass
41
+ ) -> Optional[tuple[SchemaGeneratedClass, list[str]]]:
42
+ """Return an entry point tuple of an AttackerAttachment matching the
43
+ asset provided.
44
+
45
+
46
+ Arguments:
47
+ asset - the asset to add entry point to
48
+
49
+ Return:
50
+ The entry point tuple containing the asset and the list of attack
51
+ steps if the asset has any entry points defined for this attacker
52
+ attachemnt.
53
+ None, otherwise.
54
+ """
55
+ return next((ep_tuple for ep_tuple in self.entry_points
56
+ if ep_tuple[0] == asset), None)
57
+
58
+
59
+ def add_entry_point(
39
60
  self, asset: SchemaGeneratedClass, attackstep_name: str):
40
- """Add an entrypoint to an AttackerAttachment
61
+ """Add an entry point to an AttackerAttachment
41
62
 
42
63
  self.entry_points contain tuples, first element of each tuple
43
64
  is an asset, second element is a list of attack step names that
44
65
  are entry points for the attacker.
45
66
 
46
- Args:
47
- asset - the asset to add entrypoint to
48
- attackstep_name - the name of the attack step to add as an entrypoint
67
+ Arguments:
68
+ asset - the asset to add the entry point to
69
+ attackstep_name - the name of the attack step to add as an entry point
49
70
  """
50
71
 
51
72
  logger.debug(
@@ -53,42 +74,44 @@ class AttackerAttachment:
53
74
  f'to AttackerAttachment "{self.name}".'
54
75
  )
55
76
 
56
- # Get the entrypoint tuple for the asset if it already exists
57
- entrypoint_tuple = next((ep_tuple for ep_tuple in self.entry_points
58
- if ep_tuple[0] == asset), None)
77
+ # Get the entry point tuple for the asset if it already exists
78
+ entry_point_tuple = self.get_entry_point_tuple(asset)
59
79
 
60
- if entrypoint_tuple:
61
- if attackstep_name not in entrypoint_tuple[1]:
80
+ if entry_point_tuple:
81
+ if attackstep_name not in entry_point_tuple[1]:
62
82
  # If it exists and does not already have the attack step,
63
83
  # add it
64
- entrypoint_tuple[1].append(attackstep_name)
84
+ entry_point_tuple[1].append(attackstep_name)
65
85
  else:
66
86
  logger.info(
67
87
  f'Entry point "{attackstep_name}" on asset "{asset.name}"'
68
88
  f' already existed for AttackerAttachment "{self.name}".'
69
89
  )
70
90
  else:
71
- # Otherwise, create the entrypoint tuple and the initial entry
91
+ # Otherwise, create the entry point tuple and the initial entry
72
92
  # point
73
93
  self.entry_points.append((asset, [attackstep_name]))
74
94
 
75
- def remove_entrypoint(
95
+ def remove_entry_point(
76
96
  self, asset: SchemaGeneratedClass, attackstep_name: str):
77
- """Remove an entrypoint from an AttackerAttachment if it exists"""
97
+ """Remove an entry point from an AttackerAttachment if it exists
98
+
99
+ Arguments:
100
+ asset - the asset to remove the entry point from
101
+ """
78
102
 
79
103
  logger.debug(
80
104
  f'Remove entry point "{attackstep_name}" on asset "{asset.name}" '
81
105
  f'from AttackerAttachment "{self.name}".'
82
106
  )
83
107
 
84
- # Get the entrypoint tuple for the asset if it exists
85
- entrypoint_tuple = next((ep_tuple for ep_tuple in self.entry_points
86
- if ep_tuple[0] == asset), None)
108
+ # Get the entry point tuple for the asset if it exists
109
+ entry_point_tuple = self.get_entry_point_tuple(asset)
87
110
 
88
- if entrypoint_tuple:
89
- if attackstep_name in entrypoint_tuple[1]:
111
+ if entry_point_tuple:
112
+ if attackstep_name in entry_point_tuple[1]:
90
113
  # If it exists and not already has the attack step, add it
91
- entrypoint_tuple[1].remove(attackstep_name)
114
+ entry_point_tuple[1].remove(attackstep_name)
92
115
  else:
93
116
  logger.warning(
94
117
  f'Failed to find entry point "{attackstep_name}" on '
@@ -96,8 +119,8 @@ class AttackerAttachment:
96
119
  f'"{self.name}". Nothing to remove.'
97
120
  )
98
121
 
99
- if not entrypoint_tuple[1]:
100
- self.entry_points.remove(entrypoint_tuple)
122
+ if not entry_point_tuple[1]:
123
+ self.entry_points.remove(entry_point_tuple)
101
124
  else:
102
125
  logger.warning(
103
126
  f'Failed to find entry points on asset "{asset.name}" '
@@ -205,6 +228,12 @@ class Model():
205
228
  for association in asset.associations:
206
229
  self.remove_asset_from_association(asset, association)
207
230
 
231
+ # Also remove all of the entry points
232
+ for attacker in self.attackers:
233
+ entry_point_tuple = attacker.get_entry_point_tuple(asset)
234
+ if entry_point_tuple:
235
+ attacker.entry_points.remove(entry_point_tuple)
236
+
208
237
  self.assets.remove(asset)
209
238
 
210
239
  def remove_asset_from_association(
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mal-toolbox"
3
- version = "0.1.7"
3
+ version = "0.1.9"
4
4
  authors = [
5
5
  { name="Andrei Buhaiu", email="buhaiu@kth.se" },
6
6
  { name="Giuseppe Nebbione", email="nebbione@kth.se" },
@@ -53,7 +53,6 @@ filterwarnings = "ignore:invalid escape sequence"
53
53
  exclude = [
54
54
  'maltoolbox/ingestors',
55
55
  'maltoolbox/translators',
56
- 'maltoolbox/language/compiler',
57
- 'venv'
56
+ 'maltoolbox/language/compiler'
58
57
  ]
59
58
  allow_redefinition = true
@@ -51,7 +51,7 @@ def create_association(
51
51
 
52
52
  ### Tests
53
53
 
54
- def test_attacker_attachment_add_entrypoint(model: Model):
54
+ def test_attacker_attachment_add_entry_point(model: Model):
55
55
  """"""
56
56
 
57
57
  asset1 = create_application_asset(model, "Asset1")
@@ -63,29 +63,29 @@ def test_attacker_attachment_add_entrypoint(model: Model):
63
63
  attacker1 = AttackerAttachment()
64
64
  model.add_attacker(attacker1)
65
65
 
66
- attacker1.add_entrypoint(asset1, 'read')
66
+ attacker1.add_entry_point(asset1, 'read')
67
67
  assert len(attacker1.entry_points) == 1
68
68
  assert attacker1.entry_points[0][0] == asset1
69
69
  assert attacker1.entry_points[0][1] == ['read']
70
70
 
71
- attacker1.add_entrypoint(asset1, 'access')
71
+ attacker1.add_entry_point(asset1, 'access')
72
72
  assert len(attacker1.entry_points) == 1
73
73
  assert attacker1.entry_points[0][0] == asset1
74
74
  assert attacker1.entry_points[0][1] == ['read', 'access']
75
75
 
76
76
  # Try to add already existing entry point
77
- attacker1.add_entrypoint(asset1, 'access')
77
+ attacker1.add_entry_point(asset1, 'access')
78
78
  assert len(attacker1.entry_points) == 1
79
79
  assert attacker1.entry_points[0][0] == asset1
80
80
  assert attacker1.entry_points[0][1] == ['read', 'access']
81
81
 
82
- attacker1.add_entrypoint(asset2, 'access')
82
+ attacker1.add_entry_point(asset2, 'access')
83
83
  assert len(attacker1.entry_points) == 2
84
84
  assert attacker1.entry_points[1][0] == asset2
85
85
  assert attacker1.entry_points[1][1] == ['access']
86
86
 
87
87
 
88
- def test_attacker_attachment_remove_entrypoint(model: Model):
88
+ def test_attacker_attachment_remove_entry_point(model: Model):
89
89
  """"""
90
90
 
91
91
  asset1 = create_application_asset(model, "Asset1")
@@ -97,9 +97,9 @@ def test_attacker_attachment_remove_entrypoint(model: Model):
97
97
  attacker1 = AttackerAttachment()
98
98
  model.add_attacker(attacker1)
99
99
 
100
- attacker1.add_entrypoint(asset1, 'read')
101
- attacker1.add_entrypoint(asset1, 'access')
102
- attacker1.add_entrypoint(asset2, 'access')
100
+ attacker1.add_entry_point(asset1, 'read')
101
+ attacker1.add_entry_point(asset1, 'access')
102
+ attacker1.add_entry_point(asset2, 'access')
103
103
 
104
104
  assert len(attacker1.entry_points) == 2
105
105
  assert attacker1.entry_points[0][0] == asset1
@@ -107,7 +107,7 @@ def test_attacker_attachment_remove_entrypoint(model: Model):
107
107
  assert attacker1.entry_points[1][0] == asset2
108
108
  assert attacker1.entry_points[1][1] == ['access']
109
109
 
110
- attacker1.remove_entrypoint(asset1, 'read')
110
+ attacker1.remove_entry_point(asset1, 'read')
111
111
  assert len(attacker1.entry_points) == 2
112
112
  assert attacker1.entry_points[0][0] == asset1
113
113
  assert attacker1.entry_points[0][1] == ['access']
@@ -116,29 +116,85 @@ def test_attacker_attachment_remove_entrypoint(model: Model):
116
116
 
117
117
  # Try to remove inexistent entry point, but the asset is still present in
118
118
  # the list of entry points
119
- attacker1.remove_entrypoint(asset1, 'read')
119
+ attacker1.remove_entry_point(asset1, 'read')
120
120
  assert len(attacker1.entry_points) == 2
121
121
  assert attacker1.entry_points[0][0] == asset1
122
122
  assert attacker1.entry_points[0][1] == ['access']
123
123
  assert attacker1.entry_points[1][0] == asset2
124
124
  assert attacker1.entry_points[1][1] == ['access']
125
125
 
126
- attacker1.remove_entrypoint(asset1, 'access')
126
+ attacker1.remove_entry_point(asset1, 'access')
127
127
  assert len(attacker1.entry_points) == 1
128
128
  assert attacker1.entry_points[0][0] == asset2
129
129
  assert attacker1.entry_points[0][1] == ['access']
130
130
 
131
131
  # Try to remove inexistent entry point, where the asset is no longer in
132
132
  # the list of entry points
133
- attacker1.remove_entrypoint(asset1, 'access')
133
+ attacker1.remove_entry_point(asset1, 'access')
134
134
  assert len(attacker1.entry_points) == 1
135
135
  assert attacker1.entry_points[0][0] == asset2
136
136
  assert attacker1.entry_points[0][1] == ['access']
137
137
 
138
- attacker1.remove_entrypoint(asset2, 'access')
138
+ attacker1.remove_entry_point(asset2, 'access')
139
139
  assert len(attacker1.entry_points) == 0
140
140
 
141
141
 
142
+ def test_attacker_attachment_remove_asset(model: Model):
143
+ """"""
144
+
145
+ asset1 = create_application_asset(model, "Asset1")
146
+ asset2 = create_application_asset(model, "Asset2")
147
+ model.add_asset(asset1)
148
+ model.add_asset(asset2)
149
+
150
+ attacker1 = AttackerAttachment()
151
+ model.add_attacker(attacker1)
152
+ attacker2 = AttackerAttachment()
153
+ model.add_attacker(attacker2)
154
+
155
+ attacker1.add_entry_point(asset1, 'read')
156
+ attacker1.add_entry_point(asset1, 'access')
157
+ attacker1.add_entry_point(asset2, 'access')
158
+
159
+ attacker2.add_entry_point(asset1, 'read')
160
+ attacker2.add_entry_point(asset2, 'read')
161
+ attacker2.add_entry_point(asset2, 'access')
162
+
163
+ assert len(attacker1.entry_points) == 2
164
+ assert attacker1.entry_points[0][0] == asset1
165
+ assert attacker1.entry_points[0][1] == ['read', 'access']
166
+ assert attacker1.entry_points[1][0] == asset2
167
+ assert attacker1.entry_points[1][1] == ['access']
168
+
169
+ assert len(attacker2.entry_points) == 2
170
+ assert attacker2.entry_points[0][0] == asset1
171
+ assert attacker2.entry_points[0][1] == ['read']
172
+ assert attacker2.entry_points[1][0] == asset2
173
+ assert attacker2.entry_points[1][1] == ['read', 'access']
174
+
175
+ model.remove_asset(asset2)
176
+ # All of the entry points of the asset removed should be gone, but the
177
+ # other assets should not be impacted.
178
+ assert len(attacker1.entry_points) == 1
179
+ assert attacker1.entry_points[0][0] == asset1
180
+ assert attacker1.entry_points[0][1] == ['read', 'access']
181
+
182
+ assert len(attacker2.entry_points) == 1
183
+ assert attacker2.entry_points[0][0] == asset1
184
+ assert attacker2.entry_points[0][1] == ['read']
185
+
186
+ # Try to remove inexistent entry point, where the asset is no longer in
187
+ # the list of entry points
188
+ attacker1.remove_entry_point(asset2, 'access')
189
+ assert len(attacker1.entry_points) == 1
190
+ assert attacker1.entry_points[0][0] == asset1
191
+ assert attacker1.entry_points[0][1] == ['read', 'access']
192
+
193
+ assert len(attacker2.entry_points) == 1
194
+ assert attacker2.entry_points[0][0] == asset1
195
+ assert attacker2.entry_points[0][1] == ['read']
196
+
197
+
142
198
  def test_model_add_asset(model: Model):
143
199
  """Make sure assets are added correctly"""
144
200
 
@@ -688,17 +744,17 @@ def test_model_attacker_to_dict(model: Model):
688
744
  attacker_dict = ret[1]
689
745
  assert attacker_dict.get('name') == attacker.name
690
746
 
691
- # entrypoints_dict has asset IDs as keys
692
- entrypoints_dict = attacker_dict.get('entry_points')
747
+ # entry_points_dict has asset IDs as keys
748
+ entry_points_dict = attacker_dict.get('entry_points')
693
749
 
694
750
  # attacker should be attached to p1, therefore p1s
695
- # id should be a key in the entrypoints_dict
696
- assert p1.id is not None and entrypoints_dict
697
- assert p1.id in entrypoints_dict
751
+ # id should be a key in the entry_points_dict
752
+ assert p1.id is not None and entry_points_dict
753
+ assert p1.id in entry_points_dict
698
754
 
699
- # The given steps should be inside the entrypoint of
755
+ # The given steps should be inside the entry_point of
700
756
  # the attacker for asset p1
701
- assert entrypoints_dict[p1.id]['attack_steps'] == attack_steps
757
+ assert entry_points_dict[p1.id]['attack_steps'] == attack_steps
702
758
 
703
759
 
704
760
  def test_serialize(model: Model):
@@ -1,3 +0,0 @@
1
- from .attacker import Attacker
2
- from .attackgraph import AttackGraph
3
- from .node import AttackGraphNode
File without changes
File without changes
File without changes