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

Commit 47dc1f2

Browse files
author
clittle
committed
Further refinement
1 parent d93fb1c commit 47dc1f2

4 files changed

Lines changed: 101 additions & 68 deletions

File tree

layers/README.md

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ The Layer class provides format validation and read/write capabilities to aid in
2323

2424
| method [x = Layer()]| description |
2525
|:-------|:------------|
26-
| x.load_input(_input_) | Loads an ATT&CK layer from either a dictionary or a string representation of a dictionary. |
27-
| x.load_file(_input_) | Loads an ATT&CK layer from a file location specified by the _input_. |
28-
| x.export_file(_input_) | Saves the current state of the loaded ATT&CK layer to a json file denoted by the _input_. |
29-
| x.get_dict() | Returns a representation of the current ATT&CK layer object as a dictionary. |
26+
| x.from_str(_input_) | Loads an ATT&CK layer from either a string representation of a dictionary. |
27+
| x.from_dict(_input_) | Loads an ATT&CK layer from either a dictionary. |
28+
| x.from_file(_input_) | Loads an ATT&CK layer from a file location specified by the _input_. |
29+
| x.to_file(_input_) | Saves the current state of the loaded ATT&CK layer to a json file denoted by the _input_. |
30+
| x.to_dict() | Returns a representation of the current ATT&CK layer object as a dictionary. |
31+
| x.to_str() | Returns a representation of the current ATT&CK layer object as a string representation of a dictionary. |
3032

3133
#### Example Usage
3234

@@ -43,14 +45,14 @@ example_layer_out_location = "/path/to/new/layer/file.json"
4345
from layers.core import Layer
4446

4547
layer1 = Layer(example_layer_dict) # Create a new layer and load existing data
46-
layer1.export_file(example_layer_out_location) # Write out the loaded layer to the specified file
48+
layer1.to_file(example_layer_out_location) # Write out the loaded layer to the specified file
4749

4850
layer2 = Layer() # Create a new layer object
49-
layer2.load_input(example_layer_dict) # Load layer data into existing layer object
50-
print(layer2.get_dict()) # Retrieve the loaded layer's data as a dictionary, and print it
51+
layer2.from_dict(example_layer_dict) # Load layer data into existing layer object
52+
print(layer2.to_dict()) # Retrieve the loaded layer's data as a dictionary, and print it
5153

5254
layer3 = Layer() # Create a new layer object
53-
layer3.load_file(example_layer_location) # Load layer data from a file into existing layer object
55+
layer3.from_file(example_layer_location) # Load layer data from a file into existing layer object
5456
```
5557

5658
## layerops.py
@@ -65,47 +67,48 @@ Layerops.py provides the LayerOps class, which is a way to combine layer files i
6567

6668
##### .process() Method
6769
```python
68-
x.process(data, defaults=defaults)
70+
x.process(data, default_values=default_values)
6971
```
70-
The process method applies the lambda functions stored during initialization to the layer objects in _data_. _data_ must be either a list or a dictionary of Layer objects, and is expected to match the format of the lambda equations provided during initialization.
72+
The process method applies the lambda functions stored during initialization to the layer objects in _data_. _data_ must be either a list or a dictionary of Layer objects, and is expected to match the format of the lambda equations provided during initialization. default_values is an optional dictionary argument that overrides the currently stored default
73+
values with new ones for this specific processing operation.
7174

7275
#### Example Usage
7376
```python
7477
from layers.manipulators.layerops import LayerOps
7578
from layers.core.layer import Layer
7679

7780
demo = Layer()
78-
demo.load_file("C:\Users\attack\Downloads\layer.json")
81+
demo.from_file("C:\Users\attack\Downloads\layer.json")
7982
demo2 = Layer()
80-
demo2.load_file("C:\Users\attack\Downloads\layer2.json")
83+
demo2.from_file("C:\Users\attack\Downloads\layer2.json")
8184
demo3 = Layer()
82-
demo3.load_file("C:\Users\attack\Downloads\layer3.json")
85+
demo3.from_file("C:\Users\attack\Downloads\layer3.json")
8386

8487
# Example 1) Build a LayerOps object that takes a list and averages scores across the layers
8588
lo = LayerOps(score=lambda x: sum(x) / len(x),
8689
name=lambda x: x[1],
8790
desc=lambda x: "This is an list example") # Build LayerOps object
8891
out_layer = lo.process([demo, demo2]) # Trigger processing on a list of demo and demo2 layers
89-
out_layer.export_file("C:\demo_layer1.json") # Save averaged layer to file
92+
out_layer.to_file("C:\demo_layer1.json") # Save averaged layer to file
9093
out_layer2 = lo.process([demo, demo2, demo3]) # Trigger processing on a list of demo, demo2, demo3
91-
visual_aid = out_layer2.get_dict("C:\demo_layer2.json") # Retrieve dictionary representation of processed layer
94+
visual_aid = out_layer2.to_dict() # Retrieve dictionary representation of processed layer
9295

9396
# Example 2) Build a LayerOps object that takes a dictionary and averages scores across the layers
9497
lo2 = LayerOps(score=lambda x: sum([x[y] for y in x]) / len([x[y] for y in x]),
9598
color=lambda x: x['b'],
9699
desc=lambda x: "This is a dict example") # Build LayerOps object, with lambda
97100
out_layer3 = lo2.process({'a': demo, 'b': demo2}) # Trigger processing on a dictionary of demo and demo2
98-
dict_layer = out_layer3.get_dict() # Retrieve dictionary representation of processed layer
101+
dict_layer = out_layer3.to_dict() # Retrieve dictionary representation of processed layer
99102
print(dict_layer) # Display retrieved dictionary
100103
out_layer4 = lo2.process({'a': demo, 'b': demo2, 'c': demo3})# Trigger processing on a dictionary of demo, demo2, demo3
101-
out_layer4.export_file("C:\demo_layer4.json") # Save averaged layer to file
104+
out_layer4.to_file("C:\demo_layer4.json") # Save averaged layer to file
102105

103106
# Example 3) Build a LayerOps object that takes a single element dictionary and inverts the score
104107
lo3 = LayerOps(score=lambda x: 100 - x['a'],
105108
desc= lambda x: "This is a simple example") # Build LayerOps object to invert score (0-100 scale)
106109
out_layer5 = lo3.process({'a': demo}) # Trigger processing on dictionary of demo
107-
print(out_layer5.get_dict()) # Display processed layer in dictionary form
108-
out_layer5.export_file("C:\demo_layer5.json") # Save inverted score layer to file
110+
print(out_layer5.to_dict()) # Display processed layer in dictionary form
111+
out_layer5.to_file("C:\demo_layer5.json") # Save inverted score layer to file
109112

110113
# Example 4) Build a LayerOps object that combines the comments from elements in the list, with custom defaults
111114
lo4 = LayerOps(score=lambda x: '; '.join(x),
@@ -114,5 +117,5 @@ lo4 = LayerOps(score=lambda x: '; '.join(x),
114117
},
115118
desc= lambda x: "This is a defaults example") # Build LayerOps object to combine descriptions, defaults
116119
out_layer6 = lo4.process([demo2, demo3]) # Trigger processing on a list of demo2 and demo0
117-
out_layer6.export_file("C:\demo_layer6.json") # Save combined comment layer to file
120+
out_layer6.to_file("C:\demo_layer6.json") # Save combined comment layer to file
118121
```

layers/core/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from .layer import Layer
2+
from .exceptions import *
3+
from .filter import Filter
4+
from .gradient import Gradient
5+
from .layerobj import _LayerObj
6+
from .layout import Layout
7+
from .legenditem import LegendItem
8+
from .metadata import Metadata
9+
from .technique import Technique
10+

layers/core/layer.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,55 @@
55

66

77
class Layer:
8-
def __init__(self, init_dict={}, strict=True):
8+
def __init__(self, init_data={}, strict=True):
99
"""
1010
Initialization - create a new Layer object
1111
:param init_dict: Optionally provide base Layer json or string
1212
data on initialization
1313
"""
1414
self.__layer = None
1515
self.strict = strict
16-
self.load_input(init_dict)
16+
if isinstance(init_data, str):
17+
self.from_str(init_data)
18+
else:
19+
self.from_dict(init_data)
1720

1821
@property
1922
def layer(self):
2023
if self.__layer is not None:
2124
return self.__layer
2225
return "No Layer Loaded Yet!"
2326

24-
def load_input(self, init_dict):
27+
def from_str(self, init_str):
2528
"""
26-
loads input from a string or dictionary format
27-
:param init_dit: the string or dictionary representing the layer
28-
data to be loaded
29+
Loads a raw layer string into the object
30+
:param init_str: the string representing the layer data to
31+
be loaded
2932
"""
30-
if isinstance(init_dict, str):
31-
self.data = json.loads(init_dict)
32-
else:
33-
self.data = init_dict
34-
if self.data != {}:
33+
self._data = json.loads(init_str)
34+
self._build()
35+
36+
def from_dict(self, init_dict):
37+
"""
38+
Loads a raw layer string into the object
39+
:param init_dict: the dictionary representing the layer data to
40+
be loaded
41+
"""
42+
self._data = init_dict
43+
if self._data != {}:
3544
self._build()
3645

37-
def load_file(self, filename):
46+
def from_file(self, filename):
3847
"""
3948
loads input from a layer file specified by filename
4049
:param filename: the target filename to load from
4150
"""
4251
with open(filename, 'r') as fio:
4352
raw = fio.read()
44-
self.data = json.load(raw)
53+
self._data = json.loads(raw)
4554
self._build()
4655

47-
def export_file(self, filename):
56+
def to_file(self, filename):
4857
"""
4958
saves the current state of the layer to a layer file specified by
5059
filename
@@ -61,8 +70,8 @@ def _build(self):
6170
Loads the data stored in self.data into a LayerObj (self.layer)
6271
"""
6372
try:
64-
self.__layer = _LayerObj(self.data['version'], self.data['name'],
65-
self.data['domain'])
73+
self.__layer = _LayerObj(self._data['version'], self._data['name'],
74+
self._data['domain'])
6675
except BadType or BadInput as e:
6776
handler(type(self).__name__, 'Layer is malformed: {}. '
6877
'Unable to load.'.format(e))
@@ -74,10 +83,10 @@ def _build(self):
7483
self.__layer = None
7584
return
7685

77-
for key in self.data:
86+
for key in self._data:
7887
if key not in ['version', 'name', 'domain']:
7988
try:
80-
self.__layer._linker(key, self.data[key])
89+
self.__layer._linker(key, self._data[key])
8190
except Exception as e:
8291
if self.strict:
8392
handler(type(self).__name__, "{} error. "
@@ -86,10 +95,19 @@ def _build(self):
8695
self.__layer = None
8796
return
8897

89-
def get_dict(self):
98+
def to_dict(self):
9099
"""
91100
Converts the currently loaded layer file into a dict
92101
:returns: A dict representation of the current layer object
93102
"""
94103
if self.__layer is not None:
95104
return self.__layer.get_dict()
105+
106+
def to_str(self):
107+
"""
108+
Converts the currently loaded layer file into a string
109+
representation of a dictionary
110+
:returns: A string representation of the current layer object
111+
"""
112+
if self.__layer is not None:
113+
return json.dumps(self.to_dict())

layers/manipulators/layerops.py

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ def __init__(self, score=None, comment=None, enabled=None, colors=None,
5050
:param default_values: dictionary containing desired default
5151
values for missing data element values
5252
"""
53-
self.score = score
54-
self.comment = comment
55-
self.enabled = enabled
56-
self.colors = colors
57-
self.metadata = metadata
58-
self.name = name
59-
self.desc = desc
60-
self.default_values = {
53+
self._score = score
54+
self._comment = comment
55+
self._enabled = enabled
56+
self._colors = colors
57+
self._metadata = metadata
58+
self._name = name
59+
self._desc = desc
60+
self._default_values = {
6161
"comment": "",
6262
"enabled": True,
6363
"color": "#ffffff",
@@ -66,13 +66,13 @@ def __init__(self, score=None, comment=None, enabled=None, colors=None,
6666
}
6767
if default_values is not None:
6868
for entry in default_values:
69-
self.default_values[entry] = default_values[entry]
69+
self._default_values[entry] = default_values[entry]
7070

71-
def process(self, data, defaults=None):
71+
def process(self, data, default_values=None):
7272
"""
7373
takes a list or dict of Layer objects, and processes them
7474
:param data: A dict or list of Layer objects.
75-
:param defaults: dictionary containing desired default values for
75+
:param default_values: dictionary containing desired default values for
7676
missing data element values
7777
:raises InvalidFormat: An error indicating that the layer data
7878
wasn't provided in a list or dict
@@ -94,8 +94,10 @@ def process(self, data, defaults=None):
9494
da = temp
9595
corpus = self._build_template(temp)
9696

97-
if defaults is not None:
98-
defaults = self.default_values
97+
defaults = self._default_values
98+
if default_values is not None:
99+
for entry in default_values:
100+
defaults[entry] = default_values[entry]
99101

100102
return self._compute(data, da, corpus, out, defaults)
101103

@@ -113,43 +115,43 @@ def _compute(self, data, da, corpus, out, defaults):
113115
:returns: a Layer object representing the resultant layer
114116
"""
115117
composite = copy.deepcopy(corpus)
116-
if self.score is not None:
118+
if self._score is not None:
117119
for entry in composite:
118120
entry['score'] = self._applyOperation(da, entry, 'score',
119-
self.score, defaults)
121+
self._score, defaults)
120122

121-
if self.comment is not None:
123+
if self._comment is not None:
122124
for entry in composite:
123125
entry['comment'] = self._applyOperation(da, entry, 'comment',
124-
self.comment, defaults)
126+
self._comment, defaults)
125127

126-
if self.enabled is not None:
128+
if self._enabled is not None:
127129
for entry in composite:
128130
entry['enabled'] = self._applyOperation(da, entry, 'enabled',
129-
self.enabled, defaults)
131+
self._enabled, defaults)
130132

131-
if self.colors is not None:
133+
if self._colors is not None:
132134
for entry in composite:
133135
entry['color'] = self._applyOperation(da, entry, 'color',
134-
self.colors, defaults)
136+
self._colors, defaults)
135137

136-
if self.metadata is not None:
138+
if self._metadata is not None:
137139
for entry in composite:
138140
entry['metadata'] = self._applyOperation(da, entry, 'metadata',
139-
self.metadata,
141+
self._metadata,
140142
defaults)
141143

142144
processed = copy.deepcopy(out)
143145
processed['techniques'] = composite
144-
if self.name is not None:
146+
if self._name is not None:
145147
processed['name'] = self._applyOperation(data, None, 'name',
146-
self.name, defaults,
148+
self._name, defaults,
147149
glob='name')
148150

149-
if self.desc is not None:
151+
if self._desc is not None:
150152
processed['description'] = self._applyOperation(data, None,
151153
'description',
152-
self.desc,
154+
self._desc,
153155
defaults,
154156
glob='description')
155157

@@ -310,7 +312,7 @@ def _applyOperation(self, corpus, element, name, lda, defaults, glob=None):
310312
if name in defaults:
311313
values.append(defaults[name])
312314
continue
313-
values.append(self.default_values[name])
315+
values.append(self._default_values[name])
314316
else:
315317
values = {}
316318
if glob:
@@ -333,7 +335,7 @@ def _applyOperation(self, corpus, element, name, lda, defaults, glob=None):
333335
if name in defaults:
334336
values[elm] = defaults[name]
335337
continue
336-
values[elm] = self.default_values[name]
338+
values[elm] = self._default_values[name]
337339
try:
338340
return lda(values)
339341
except IndexError and KeyError:

0 commit comments

Comments
 (0)