mal-toolbox 0.1.0__tar.gz → 0.1.1__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.0/mal_toolbox.egg-info → mal_toolbox-0.1.1}/PKG-INFO +1 -1
  2. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1/mal_toolbox.egg-info}/PKG-INFO +1 -1
  3. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/__init__.py +2 -2
  4. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/languagegraph.py +4 -4
  5. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/model.py +117 -61
  6. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/pyproject.toml +1 -1
  7. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/AUTHORS +0 -0
  8. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/LICENSE +0 -0
  9. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/README.md +0 -0
  10. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/mal_toolbox.egg-info/SOURCES.txt +0 -0
  11. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/mal_toolbox.egg-info/dependency_links.txt +0 -0
  12. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/mal_toolbox.egg-info/requires.txt +0 -0
  13. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/mal_toolbox.egg-info/top_level.txt +0 -0
  14. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/__main__.py +0 -0
  15. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/__init__.py +0 -0
  16. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/analyzers/__init__.py +0 -0
  17. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/analyzers/apriori.py +0 -0
  18. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/attacker.py +0 -0
  19. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/attackgraph.py +0 -0
  20. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/node.py +0 -0
  21. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/attackgraph/query.py +0 -0
  22. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/default.conf +0 -0
  23. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/exceptions.py +0 -0
  24. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/file_utils.py +0 -0
  25. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/ingestors/__init__.py +0 -0
  26. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/ingestors/neo4j.py +0 -0
  27. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/__init__.py +0 -0
  28. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/classes_factory.py +0 -0
  29. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/compiler/__init__.py +0 -0
  30. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/compiler/mal_lexer.py +0 -0
  31. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/compiler/mal_parser.py +0 -0
  32. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/compiler/mal_visitor.py +0 -0
  33. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/language/specification.py +0 -0
  34. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/translators/__init__.py +0 -0
  35. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/translators/securicad.py +0 -0
  36. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/translators/updater.py +0 -0
  37. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/maltoolbox/wrappers.py +0 -0
  38. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/setup.cfg +0 -0
  39. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/tests/test_model.py +0 -0
  40. {mal_toolbox-0.1.0 → mal_toolbox-0.1.1}/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.0
3
+ Version: 0.1.1
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mal-toolbox
3
- Version: 0.1.0
3
+ Version: 0.1.1
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
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # MAL Toolbox v0.1.0
2
+ # MAL Toolbox v0.1.1
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.0'
24
+ __version__ = '0.1.1'
25
25
  __authors__ = ['Andrei Buhaiu',
26
26
  'Giuseppe Nebbione',
27
27
  'Nikolaos Kakouros',
@@ -622,7 +622,7 @@ class LanguageGraph():
622
622
  new_dep_chain,
623
623
  attack_step)
624
624
 
625
- case 'subType':
625
+ case 'subtype':
626
626
  # Create a subType tuple entry that applies to the next
627
627
  # component of the step expression and changes the target
628
628
  # asset to the subasset.
@@ -678,7 +678,7 @@ class LanguageGraph():
678
678
 
679
679
  case _:
680
680
  logger.error(
681
- 'Unknown attack step type: %s', step_expression["type"]
681
+ 'Unknown attack step type: "%s"', step_expression["type"]
682
682
  )
683
683
  return (None, None, None)
684
684
 
@@ -748,7 +748,7 @@ class LanguageGraph():
748
748
  new_dep_chain
749
749
  )
750
750
 
751
- case 'subType':
751
+ case 'subtype':
752
752
  #TODO FIX BUG: new_dep_chain is undefined
753
753
  # result_reverse_chain = self.reverse_dep_chain(
754
754
  # new_dep_chain.next_link,
@@ -763,7 +763,7 @@ class LanguageGraph():
763
763
  return reverse_chain
764
764
 
765
765
  case _:
766
- msg = 'Unknown assoc chain element %s'
766
+ msg = 'Unknown assoc chain element "%s"'
767
767
  logger.error(msg, dep_chain.type)
768
768
  raise LanguageGraphAssociationError(msg % dep_chain.type)
769
769
 
@@ -162,11 +162,12 @@ class Model():
162
162
  f'Association is not part of model "{self.name}".'
163
163
  )
164
164
 
165
- first_field_name, second_field_name = association._properties.keys()
166
- first_field = getattr(association, first_field_name)
167
- second_field = getattr(association, second_field_name)
165
+ left_field_name, right_field_name = \
166
+ self.get_association_field_names(association)
167
+ left_field = getattr(association, left_field_name)
168
+ right_field = getattr(association, right_field_name)
168
169
  found = False
169
- for field in [first_field, second_field]:
170
+ for field in [left_field, right_field]:
170
171
  if asset in field:
171
172
  found = True
172
173
  if len(field) == 1:
@@ -202,7 +203,8 @@ class Model():
202
203
 
203
204
 
204
205
  # Check for duplicate assets in each field
205
- left_field_name, right_field_name = association._properties.keys()
206
+ left_field_name, right_field_name = \
207
+ self.get_association_field_names(association)
206
208
 
207
209
  for field_name in (left_field_name, right_field_name):
208
210
  field_assets = getattr(association, field_name)
@@ -251,8 +253,7 @@ class Model():
251
253
  # Optional field for extra association data
252
254
  association.extras = {}
253
255
 
254
- # Field names are the two first values in _properties
255
- field_names = list(vars(association)['_properties'])[0:2]
256
+ field_names = self.get_association_field_names(association)
256
257
 
257
258
  # Add the association to all of the included assets
258
259
  for field_name in field_names:
@@ -282,22 +283,20 @@ class Model():
282
283
  f'Association is not part of model "{self.name}".'
283
284
  )
284
285
 
285
- # An assocation goes from one field to another,
286
- # both fields has a field_name and a list of assets
287
- first_field_name, second_field_name = association._properties.keys()
288
- first_field = getattr(association, first_field_name)
289
- second_field = getattr(association, second_field_name)
286
+ left_field_name, right_field_name = \
287
+ self.get_association_field_names(association)
288
+ left_field = getattr(association, left_field_name)
289
+ right_field = getattr(association, right_field_name)
290
290
 
291
- for asset in first_field:
291
+ for asset in left_field:
292
292
  assocs = list(asset.associations)
293
293
  assocs.remove(association)
294
294
  asset.associations = assocs
295
295
 
296
- for asset in second_field:
296
+ for asset in right_field:
297
297
  # In fringe cases we may have reflexive associations where the
298
- # first element was removed from the association already. But
299
- # generally the association should exist for the second element
300
- # too.
298
+ # association was already removed when processing the left field
299
+ # assets therefore we have to check if it is still in the list.
301
300
  if association in asset.associations:
302
301
  assocs = list(asset.associations)
303
302
  assocs.remove(association)
@@ -403,13 +402,89 @@ class Model():
403
402
  self, association_type, left_asset, right_asset
404
403
  ):
405
404
  """Return True if the association already exists between the assets"""
405
+ logger.debug(
406
+ 'Check to see if an association of type "%s" '
407
+ 'already exists between "%s" and "%s".',
408
+ association_type, left_asset.name, right_asset.name
409
+ )
406
410
  associations = self._type_to_association.get(association_type, [])
407
411
  for association in associations:
408
- field_name1, field_name2 = association._properties.keys()
409
- if left_asset in getattr(association, field_name1):
410
- if right_asset in getattr(association, field_name2):
412
+ left_field_name, right_field_name = \
413
+ self.get_association_field_names(association)
414
+ if (left_asset.id in [asset.id for asset in \
415
+ getattr(association, left_field_name)] and \
416
+ right_asset.id in [asset.id for asset in \
417
+ getattr(association, right_field_name)]):
418
+ logger.debug(
419
+ 'An association of type "%s" '
420
+ 'already exists between "%s" and "%s".',
421
+ association_type, left_asset.name, right_asset.name
422
+ )
411
423
  return True
412
- return False
424
+ logger.debug(
425
+ 'No association of type "%s" '
426
+ 'exists between "%s" and "%s".',
427
+ association_type, left_asset.name, right_asset.name
428
+ )
429
+ return False
430
+
431
+ def get_asset_defenses(
432
+ self,
433
+ asset: SchemaGeneratedClass,
434
+ include_defaults: bool = False
435
+ ):
436
+ """
437
+ Get the two field names of the association as a list.
438
+ Arguments:
439
+ asset - the asset to fetch the defenses for
440
+ include_defaults - if not True the defenses that have default
441
+ values will not be included in the list
442
+
443
+ Return:
444
+ A dictionary containing the defenses of the asset
445
+ """
446
+
447
+ defenses = {}
448
+ for key, value in asset._properties.items():
449
+ property_schema = (
450
+ self.lang_classes_factory.json_schema['definitions']['LanguageAsset']
451
+ ['definitions'][asset.type]['properties'][key]
452
+ )
453
+
454
+ if "maximum" not in property_schema:
455
+ # Check if property is a defense by looking up defense
456
+ # specific key. Skip if it is not a defense.
457
+ continue
458
+
459
+ logger.debug(
460
+ 'Translating %s: %s defense to dictionary.',
461
+ key,
462
+ value
463
+ )
464
+
465
+ if not include_defaults and value == value.default():
466
+ # Skip the defense values if they are the default ones.
467
+ continue
468
+
469
+ defenses[key] = float(value)
470
+
471
+ return defenses
472
+
473
+ def get_association_field_names(
474
+ self,
475
+ association: SchemaGeneratedClass
476
+ ):
477
+ """
478
+ Get the two field names of the association as a list.
479
+ Arguments:
480
+ association - the association to fetch the field names for
481
+
482
+ Return:
483
+ A two item list containing the field names of the association.
484
+ """
485
+
486
+ return association._properties.keys()
487
+
413
488
 
414
489
  def get_associated_assets_by_field_name(
415
490
  self,
@@ -421,7 +496,7 @@ class Model():
421
496
 
422
497
  Arguments:
423
498
  asset - the asset whose fields we are interested in
424
- field_name - the field name we are looking for
499
+ field_name - the field name we are looking for
425
500
 
426
501
  Return:
427
502
  A list of assets associated with the asset given that match the
@@ -434,22 +509,19 @@ class Model():
434
509
  )
435
510
  associated_assets = []
436
511
  for association in asset.associations:
437
- # Determine which two of the fields leads away from the asset.
438
- # This is particularly relevant for associations between two
439
- # assets of the same type.
440
- field_name1, field_name2 = association._properties.keys()
441
-
442
- if asset in getattr(association, field_name1):
443
- # If the asset is in the first field,
444
- # the second one must lead away from it
445
- field_name_away = field_name2
512
+ # Determine which two of the fields matches the asset given.
513
+ # The other field will provide the associated assets.
514
+ left_field_name, right_field_name = \
515
+ self.get_association_field_names(association)
516
+
517
+ if asset in getattr(association, left_field_name):
518
+ opposite_field_name = right_field_name
446
519
  else:
447
- # otherwise the first field must lead away
448
- field_name_away = field_name1
520
+ opposite_field_name = left_field_name
449
521
 
450
- if field_name_away == field_name:
522
+ if opposite_field_name == field_name:
451
523
  associated_assets.extend(
452
- getattr(association, field_name_away)
524
+ getattr(association, opposite_field_name)
453
525
  )
454
526
 
455
527
  return associated_assets
@@ -463,37 +535,20 @@ class Model():
463
535
  Return: tuple with name of asset and the asset as dict
464
536
  """
465
537
 
466
- defenses = {}
467
538
  logger.debug(
468
539
  'Translating "%s"(%d) to dictionary.',
469
540
  asset.name,
470
541
  asset.id
471
542
  )
472
543
 
473
- for key, value in asset._properties.items():
474
- property_schema = (
475
- self.lang_classes_factory.json_schema['definitions']['LanguageAsset']
476
- ['definitions'][asset.type]['properties'][key]
477
- )
478
-
479
- if "maximum" not in property_schema:
480
- # Check if property is a defense by looking up defense
481
- # specific key. Skip if it is not a defense.
482
- continue
483
-
484
- logger.debug('Translating %s: %s defense to dictionary.', key, value)
485
-
486
- if value == value.default():
487
- # Skip the defense values if they are the default ones.
488
- continue
489
-
490
- defenses[key] = float(value)
491
544
 
492
545
  asset_dict: dict[str, Any] = {
493
546
  'name': str(asset.name),
494
547
  'type': str(asset.type)
495
548
  }
496
549
 
550
+ defenses = self.get_asset_defenses(asset)
551
+
497
552
  if defenses:
498
553
  asset_dict['defenses'] = defenses
499
554
 
@@ -513,17 +568,18 @@ class Model():
513
568
  Returns the association serialized to a dict
514
569
  """
515
570
 
516
- first_field_name, second_field_name = association._properties.keys()
517
- first_field = getattr(association, first_field_name)
518
- second_field = getattr(association, second_field_name)
571
+ left_field_name, right_field_name = \
572
+ self.get_association_field_names(association)
573
+ left_field = getattr(association, left_field_name)
574
+ right_field = getattr(association, right_field_name)
519
575
 
520
576
  association_dict = {
521
577
  association.__class__.__name__ :
522
578
  {
523
- str(first_field_name):
524
- [int(asset.id) for asset in first_field],
525
- str(second_field_name):
526
- [int(asset.id) for asset in second_field]
579
+ str(left_field_name):
580
+ [int(asset.id) for asset in left_field],
581
+ str(right_field_name):
582
+ [int(asset.id) for asset in right_field]
527
583
  }
528
584
  }
529
585
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mal-toolbox"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  authors = [
5
5
  { name="Andrei Buhaiu", email="buhaiu@kth.se" },
6
6
  { name="Giuseppe Nebbione", email="nebbione@kth.se" },
File without changes
File without changes
File without changes
File without changes