1
+ import logging
1
2
import os
3
+ import zipfile
4
+ from collections import defaultdict
2
5
from dataclasses import dataclass , field
3
6
from itertools import chain
4
- from typing import List , Optional , Dict , Iterable
7
+ from typing import List , Optional , Dict , Iterable , Tuple
5
8
6
- from maltego_trx .utils import filter_unique , pascal_case_to_title , escape_csv_fields , export_as_csv , serialize_bool , \
7
- name_to_path
9
+ from maltego_trx .mtz import (
10
+ create_local_server_xml ,
11
+ create_settings_xml ,
12
+ create_transform_xml ,
13
+ create_transform_set_xml ,
14
+ )
15
+ from maltego_trx .utils import (
16
+ filter_unique ,
17
+ pascal_case_to_title ,
18
+ escape_csv_fields ,
19
+ export_as_csv ,
20
+ serialize_bool ,
21
+ name_to_path ,
22
+ serialize_xml ,
23
+ )
8
24
9
- TRANSFORMS_CSV_HEADER = "Owner,Author,Disclaimer,Description,Version," \
10
- "Name,UIName,URL,entityName," \
11
- "oAuthSettingId,transformSettingIDs,seedIDs"
25
+ TRANSFORMS_CSV_HEADER = (
26
+ "Owner,Author,Disclaimer,Description,Version,"
27
+ "Name,UIName,URL,entityName,"
28
+ "oAuthSettingId,transformSettingIDs,seedIDs"
29
+ )
12
30
SETTINGS_CSV_HEADER = "Name,Type,Display,DefaultValue,Optional,Popup"
13
31
14
32
33
+ @dataclass (frozen = True )
34
+ class TransformSet :
35
+ name : str
36
+ description : str
37
+
38
+
15
39
@dataclass ()
16
40
class TransformMeta :
17
41
class_name : str
@@ -20,9 +44,10 @@ class TransformMeta:
20
44
description : str
21
45
output_entities : List [str ]
22
46
disclaimer : str
47
+ transform_set : TransformSet
23
48
24
49
25
- @dataclass ()
50
+ @dataclass (frozen = True )
26
51
class TransformSetting :
27
52
name : str
28
53
display_name : str
@@ -49,87 +74,196 @@ class TransformRegistry:
49
74
host_url : str
50
75
seed_ids : List [str ]
51
76
52
- version : str = ' 0.1'
77
+ version : str = " 0.1"
53
78
display_name_suffix : str = ""
54
79
55
80
global_settings : List [TransformSetting ] = field (default_factory = list )
56
81
oauth_settings_id : Optional [str ] = ""
57
82
58
83
transform_metas : Dict [str , TransformMeta ] = field (init = False , default_factory = dict )
59
- transform_settings : Dict [str , List [TransformSetting ]] = field (init = False , default_factory = dict )
84
+ transform_settings : Dict [str , List [TransformSetting ]] = field (
85
+ init = False , default_factory = dict
86
+ )
87
+ transform_sets : Dict [TransformSet , List [str ]] = field (
88
+ init = False , default_factory = lambda : defaultdict (list )
89
+ )
60
90
61
- def register_transform (self , display_name : str , input_entity : str , description : str ,
62
- settings : List [TransformSetting ] = None , output_entities : List [str ] = None ,
63
- disclaimer : str = "" ):
64
- """ This method can be used as a decorator on transform classes. The data will be used to fill out csv config
65
- files to be imported into a TDS.
91
+ def register_transform (
92
+ self ,
93
+ display_name : str ,
94
+ input_entity : str ,
95
+ description : str ,
96
+ settings : List [TransformSetting ] = None ,
97
+ output_entities : List [str ] = None ,
98
+ disclaimer : str = "" ,
99
+ transform_set : TransformSet = None ,
100
+ ):
101
+ """This method can be used as a decorator on transform classes. The data will be used to fill out csv config
102
+ files to be imported into a TDS.
66
103
"""
67
104
68
105
def decorated (transform_callable : object ):
69
106
cleaned_transform_name = name_to_path (transform_callable .__name__ )
70
107
display = display_name or pascal_case_to_title (transform_callable .__name__ )
71
108
72
- meta = TransformMeta (cleaned_transform_name ,
73
- display , input_entity ,
74
- description ,
75
- output_entities or [],
76
- disclaimer )
109
+ meta = TransformMeta (
110
+ cleaned_transform_name ,
111
+ display ,
112
+ input_entity ,
113
+ description ,
114
+ output_entities or [],
115
+ disclaimer ,
116
+ transform_set = transform_set ,
117
+ )
77
118
self .transform_metas [cleaned_transform_name ] = meta
78
119
79
120
if settings :
80
121
self .transform_settings [cleaned_transform_name ] = settings
81
122
123
+ if transform_set :
124
+ self .transform_sets [transform_set ].append (cleaned_transform_name )
125
+
82
126
return transform_callable
83
127
84
128
return decorated
85
129
86
- def write_transforms_config (self , config_path : str = "./transforms.csv" , csv_line_limit : int = 100 ):
87
- """Exports the collected transform metadata as a csv-file to config_path"""
130
+ def _create_transforms_config (self ) -> Iterable [str ]:
88
131
global_settings_full_names = [gs .id for gs in self .global_settings ]
89
132
90
- csv_lines = []
91
133
for transform_name , transform_meta in self .transform_metas .items ():
92
- meta_settings = [setting .id for setting in
93
- self .transform_settings .get (transform_name , [])]
134
+ meta_settings = [
135
+ setting .id
136
+ for setting in self .transform_settings .get (transform_name , [])
137
+ ]
94
138
95
139
transform_row = [
96
- self .owner ,
97
- self .author ,
98
- transform_meta .disclaimer ,
99
- transform_meta .description ,
100
- self .version ,
101
- transform_name ,
102
- transform_meta .display_name + self .display_name_suffix ,
103
- os .path .join (self .host_url , "run" , transform_name ),
104
- transform_meta .input_entity ,
105
- ";" .join (self .oauth_settings_id ),
106
- # combine global and transform scoped settings
107
- ";" .join (chain (meta_settings , global_settings_full_names )),
108
- ";" .join (self .seed_ids )
140
+ self .owner ,
141
+ self .author ,
142
+ transform_meta .disclaimer ,
143
+ transform_meta .description ,
144
+ self .version ,
145
+ transform_name ,
146
+ transform_meta .display_name + self .display_name_suffix ,
147
+ os .path .join (self .host_url , "run" , transform_name ),
148
+ transform_meta .input_entity ,
149
+ ";" .join (self .oauth_settings_id ),
150
+ # combine global and transform scoped settings
151
+ ";" .join (chain (meta_settings , global_settings_full_names )),
152
+ ";" .join (self .seed_ids ),
109
153
]
110
154
111
155
escaped_fields = escape_csv_fields (* transform_row )
112
- csv_lines .append ("," .join (escaped_fields ))
156
+ yield "," .join (escaped_fields )
157
+
158
+ def write_transforms_config (
159
+ self , config_path : str = "./transforms.csv" , csv_line_limit : int = 100
160
+ ):
161
+ """Exports the collected transform metadata as a csv-file to config_path"""
113
162
114
- export_as_csv ( TRANSFORMS_CSV_HEADER , csv_lines , config_path , csv_line_limit )
163
+ csv_lines = self . _create_transforms_config ( )
115
164
116
- def write_settings_config (self , config_path : str = "./settings.csv" , csv_line_limit : int = 100 ):
117
- """Exports the collected settings metadata as a csv-file to config_path"""
118
- chained_settings = chain (self .global_settings , * list (self .transform_settings .values ()))
119
- unique_settings : Iterable [TransformSetting ] = filter_unique (lambda s : s .name , chained_settings )
165
+ export_as_csv (
166
+ TRANSFORMS_CSV_HEADER , tuple (csv_lines ), config_path , csv_line_limit
167
+ )
168
+
169
+ def _create_settings_config (self ) -> Iterable [str ]:
170
+ chained_settings = chain (
171
+ self .global_settings , * list (self .transform_settings .values ())
172
+ )
173
+ unique_settings : Iterable [TransformSetting ] = filter_unique (
174
+ lambda s : s .name , chained_settings
175
+ )
120
176
121
- csv_lines = []
122
177
for setting in unique_settings :
123
178
setting_row = [
124
- setting .id ,
125
- setting .setting_type ,
126
- setting .display_name ,
127
- setting .default_value or "" ,
128
- serialize_bool (setting .optional , ' True' , ' False' ),
129
- serialize_bool (setting .popup , ' Yes' , 'No' )
179
+ setting .id ,
180
+ setting .setting_type ,
181
+ setting .display_name ,
182
+ setting .default_value or "" ,
183
+ serialize_bool (setting .optional , " True" , " False" ),
184
+ serialize_bool (setting .popup , " Yes" , "No" ),
130
185
]
131
186
132
187
escaped_fields = escape_csv_fields (* setting_row )
133
- csv_lines .append ("," .join (escaped_fields ))
188
+ yield "," .join (escaped_fields )
189
+
190
+ def write_settings_config (
191
+ self , config_path : str = "./settings.csv" , csv_line_limit : int = 100
192
+ ):
193
+ """Exports the collected settings metadata as a csv-file to config_path"""
194
+
195
+ csv_lines = self ._create_settings_config ()
196
+
197
+ export_as_csv (
198
+ SETTINGS_CSV_HEADER , tuple (csv_lines ), config_path , csv_line_limit
199
+ )
200
+
201
+ def _create_local_mtz (
202
+ self ,
203
+ working_dir : str = "." ,
204
+ command : str = "python3" ,
205
+ params : str = "project.py" ,
206
+ debug : bool = True ,
207
+ ) -> Iterable [Tuple [str , str ]]:
208
+ working_dir = os .path .abspath (working_dir )
209
+ if self .global_settings :
210
+ logging .warning (
211
+ f"Settings are not supported with local transforms. "
212
+ f"Global settings are: { ', ' .join (map (lambda s : s .name , self .global_settings ))} "
213
+ )
214
+
215
+ """Creates an .mtz for bulk importing local transforms"""
216
+ server_xml = create_local_server_xml (self .transform_metas .keys ())
217
+
218
+ server_xml_str = serialize_xml (server_xml )
219
+ yield "Servers/Local.tas" , server_xml_str
220
+
221
+ for name , meta in self .transform_metas .items ():
222
+ settings_xml = create_settings_xml (
223
+ working_dir , command , f"{ params } local { name } " , debug
224
+ )
225
+ settings_xml_str = serialize_xml (settings_xml )
226
+
227
+ tx_settings = self .transform_settings .get (name )
228
+ if tx_settings :
229
+ logging .warning (
230
+ "Settings are not supported with local transforms. "
231
+ f"Transform '{ meta .display_name } ' has: { ', ' .join (map (lambda s : s .name , tx_settings ))} "
232
+ )
233
+
234
+ xml = create_transform_xml (
235
+ name ,
236
+ meta .display_name ,
237
+ meta .description ,
238
+ meta .input_entity ,
239
+ self .author ,
240
+ )
241
+
242
+ xml_str = serialize_xml (xml )
243
+
244
+ yield f"TransformRepositories/Local/{ name } .transform" , xml_str
245
+ yield f"TransformRepositories/Local/{ name } .transformsettings" , settings_xml_str
246
+
247
+ for transform_set , transforms in self .transform_sets .items ():
248
+ set_xml = create_transform_set_xml (
249
+ transform_set .name , transform_set .description , transforms
250
+ )
251
+
252
+ set_xml_str = serialize_xml (set_xml )
253
+
254
+ yield f"TransformSets/{ transform_set .name } .set" , set_xml_str
255
+
256
+ def write_local_mtz (
257
+ self ,
258
+ mtz_path : str = "./local.mtz" ,
259
+ working_dir : str = "." ,
260
+ command : str = "python3" ,
261
+ params : str = "project.py" ,
262
+ debug : bool = True ,
263
+ ):
134
264
135
- export_as_csv (SETTINGS_CSV_HEADER , csv_lines , config_path , csv_line_limit )
265
+ with zipfile .ZipFile (mtz_path , "w" ) as mtz :
266
+ for path , content in self ._create_local_mtz (
267
+ working_dir , command , params , debug
268
+ ):
269
+ mtz .writestr (path , content )
0 commit comments