Skip to content
This repository was archived by the owner on Dec 11, 2023. It is now read-only.

Commit c18ab47

Browse files
author
clittle
committed
Added support for versions and domain changes in v4
1 parent 8b2e017 commit c18ab47

5 files changed

Lines changed: 112 additions & 14 deletions

File tree

layers/README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This folder contains modules and scripts for working with ATT&CK Navigator layer
1212
| [legenditem](core/legenditem.py) | Implements a basic [legenditem object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#legenditem-object-properties). |
1313
| [metadata](core/metadata.py) | Implements a basic [metadata object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#metadata-object-properties). |
1414
| [technique](core/technique.py) | Implements a basic [technique object](https://github.com/mitre-attack/attack-navigator/blob/develop/layers/LAYERFORMATv3.md#technique-object-properties). |
15-
15+
| [version](core/versions.py) | Impelments a basic [version object]().|
1616
#### Manipulator Scripts
1717
| script | description |
1818
|:-------|:------------|
@@ -36,7 +36,7 @@ This folder contains modules and scripts for working with ATT&CK Navigator layer
3636
| [layerExporter_cli.py](layerExporter_cli.py) | A commandline utility to export Layer files to excel or svg formats using the exporter tools. Run with `-h` for usage. |
3737

3838
## Layer
39-
The Layer class provides format validation and read/write capabilities to aid in working with ATT&CK Navigator Layers in python. It is the primary interface through which other Layer-related classes defined in the core module should be used. The Layer class API and a usage example are below.
39+
The Layer class provides format validation and read/write capabilities to aid in working with ATT&CK Navigator Layers in python. It is the primary interface through which other Layer-related classes defined in the core module should be used. The Layer class API and a usage example are below. The class currently supports version 3 and 4 of the ATT&CK Layer spec, and will upgrade version 3 layers into compatible version 4 ones whenever possible.
4040

4141
| method [x = Layer()]| description |
4242
|:-------|:------------|
@@ -56,6 +56,14 @@ example_layer_dict = {
5656
"domain": "mitre-enterprise"
5757
}
5858

59+
example_layer4_dict = {
60+
"name": "layer v4 example",
61+
"versions" : {
62+
"layer" : "4.0"
63+
},
64+
"domain": "enterprise-attack"
65+
}
66+
5967
example_layer_location = "/path/to/layer/file.json"
6068
example_layer_out_location = "/path/to/new/layer/file.json"
6169

@@ -65,7 +73,7 @@ layer1 = Layer(example_layer_dict) # Create a new layer and load ex
6573
layer1.to_file(example_layer_out_location) # Write out the loaded layer to the specified file
6674

6775
layer2 = Layer() # Create a new layer object
68-
layer2.from_dict(example_layer_dict) # Load layer data into existing layer object
76+
layer2.from_dict(example_layer4_dict) # Load layer data into existing layer object
6977
print(layer2.to_dict()) # Retrieve the loaded layer's data as a dictionary, and print it
7078

7179
layer3 = Layer() # Create a new layer object

layers/core/filter.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ def get_dict(self):
4646
if entry == 'domain':
4747
continue
4848
if listing[entry] != UNSETVALUE:
49-
temp[entry.split(type(self).__name__ + '__')[-1]] \
50-
= listing[entry]
49+
subname = entry.split('__')[-1]
50+
if subname != 'stages':
51+
temp[subname] = listing[entry]
5152
if len(temp) > 0:
5253
return temp
5354

layers/core/layer.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,17 @@ def _build(self):
7575
Loads the data stored in self.data into a LayerObj (self.layer)
7676
"""
7777
try:
78-
self.__layer = _LayerObj(self._data['version'], self._data['name'],
78+
if 'version' in self._data:
79+
ver_obj = dict(layer=self._data['version'])
80+
else:
81+
ver_obj = self._data['versions']
82+
except:
83+
handler(type(self).__name__, 'Layer version is malformed. '
84+
'Unable to load')
85+
self.__layer = None
86+
return
87+
try:
88+
self.__layer = _LayerObj(ver_obj, self._data['name'],
7989
self._data['domain'])
8090
except BadType or BadInput as e:
8191
handler(type(self).__name__, 'Layer is malformed: {}. '
@@ -89,7 +99,7 @@ def _build(self):
8999
return
90100

91101
for key in self._data:
92-
if key not in ['version', 'name', 'domain']:
102+
if key not in ['version', 'versions', 'name', 'domain']:
93103
try:
94104
self.__layer._linker(key, self._data[key])
95105
except Exception as e:

layers/core/layerobj.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from ..core.gradient import Gradient
66
from ..core.legenditem import LegendItem
77
from ..core.metadata import Metadata
8+
from ..core.versions import Versions
89
from ..core.exceptions import UNSETVALUE, typeChecker, BadInput, handler, \
910
categoryChecker, UnknownLayerProperty
1011
except ValueError:
@@ -14,11 +15,12 @@
1415
from core.gradient import Gradient
1516
from core.legenditem import LegendItem
1617
from core.metadata import Metadata
18+
from core.versions import Versions
1719
from core.exceptions import UNSETVALUE, typeChecker, BadInput, handler, \
1820
categoryChecker, UnknownLayerProperty
1921

2022
class _LayerObj:
21-
def __init__(self, version, name, domain):
23+
def __init__(self, versions, name, domain):
2224
"""
2325
Initialization - Creates a layer object
2426
@@ -27,7 +29,7 @@ def __init__(self, version, name, domain):
2729
:param domain: The domain for this layer (mitre-enterprise
2830
or mitre-mobile)
2931
"""
30-
self.version = version
32+
self.versions = versions
3133
self.name = name
3234
self.__description = UNSETVALUE
3335
self.domain = domain
@@ -46,13 +48,28 @@ def __init__(self, version, name, domain):
4648

4749
@property
4850
def version(self):
49-
return self.__version
51+
return self.__versions.layer
5052

5153
@version.setter
5254
def version(self, version):
5355
typeChecker(type(self).__name__, version, str, "version")
5456
categoryChecker(type(self).__name__, version, ["3.0", "4.0"], "version")
55-
self.__version = version
57+
self.__versions.layer = version
58+
59+
@property
60+
def versions(self):
61+
return self.__versions
62+
63+
@versions.setter
64+
def versions(self, versions):
65+
typeChecker(type(self).__name__, versions, dict, "version")
66+
attack = None
67+
nav = None
68+
if 'attack' in versions:
69+
attack = versions['attack']
70+
if 'navigator' in versions:
71+
nav = versions['navigator']
72+
self.__versions = Versions(versions['layer'], attack, nav)
5673

5774
@property
5875
def name(self):
@@ -70,8 +87,11 @@ def domain(self):
7087
@domain.setter
7188
def domain(self, domain):
7289
typeChecker(type(self).__name__, domain, str, "domain")
73-
categoryChecker(type(self).__name__, domain, ["mitre-enterprise",
74-
"mitre-mobile"],
90+
dom = domain
91+
if dom.startswith('mitre'):
92+
dom = dom.split('-')[-1] + '-attack'
93+
categoryChecker(type(self).__name__, dom, ["enterprise-attack",
94+
"mobile-attack"],
7595
"domain")
7696
self.__domain = domain
7797

@@ -301,7 +321,7 @@ def get_dict(self):
301321
Converts the currently loaded layer into a dict
302322
:returns: A dict representation of the current layer object
303323
"""
304-
temp = dict(name=self.name, version=self.version, domain=self.domain)
324+
temp = dict(name=self.name, versions=self.versions.get_dict(), domain=self.domain)
305325

306326
if self.description:
307327
temp['description'] = self.description

layers/core/versions.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
try:
2+
from ..core.exceptions import typeChecker, categoryChecker, UNSETVALUE
3+
except ValueError:
4+
from core.exceptions import typeChecker, categoryChecker, UNSETVALUE
5+
6+
7+
class Versions:
8+
def __init__(self, layer="4.0", attack=UNSETVALUE, navigator=UNSETVALUE):
9+
"""
10+
Initialization - Creates a v4 Versions object
11+
12+
:param layer: The layer version
13+
:param attack: The attack version
14+
:param navigator: The navigator version
15+
"""
16+
self.layer = layer
17+
self.attack = attack
18+
self.navigator = navigator
19+
20+
@property
21+
def attack(self):
22+
return self.__attack
23+
24+
@attack.setter
25+
def attack(self, attack):
26+
typeChecker(type(self).__name__, attack, str, "attack")
27+
self.__attack = attack
28+
29+
@property
30+
def navigator(self):
31+
return self.__navigator
32+
33+
@navigator.setter
34+
def navigator(self, navigator):
35+
typeChecker(type(self).__name__, navigator, str, "navigator")
36+
self.__navigator = navigator
37+
38+
@property
39+
def layer(self):
40+
return self.__layer
41+
42+
@layer.setter
43+
def layer(self, layer):
44+
typeChecker(type(self).__name__, layer, str, "layer")
45+
categoryChecker(type(self).__name__, layer, ["3.0", "4.0"], "version")
46+
self.__layer = layer
47+
48+
def get_dict(self):
49+
"""
50+
Converts the currently loaded data into a dict
51+
:returns: A dict representation of the local Versions object
52+
"""
53+
temp = dict()
54+
listing = vars(self)
55+
for entry in listing:
56+
if listing[entry] != UNSETVALUE:
57+
subname = entry.split('__')[-1]
58+
temp[subname] = listing[entry]
59+
return temp

0 commit comments

Comments
 (0)