dictature 0.9.3__py3-none-any.whl → 0.9.5__py3-none-any.whl

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.
@@ -1,14 +1,17 @@
1
- from re import sub
2
- from json import dumps, loads
3
1
  from pathlib import Path
4
2
  from typing import Iterable, Union
5
3
  from shutil import rmtree
6
4
 
7
- from .mock import DictatureTableMock, DictatureBackendMock, Value, ValueMode
5
+ from .mock import DictatureTableMock, DictatureBackendMock, Value, ValueMode, ValueSerializer, ValueSerializerMode
8
6
 
9
7
 
10
8
  class DictatureBackendDirectory(DictatureBackendMock):
11
9
  def __init__(self, directory: Union[Path, str], dir_prefix: str = 'db_') -> None:
10
+ """
11
+ Create a new directory backend
12
+ :param directory: directory to store the data
13
+ :param dir_prefix: prefix for the directories of the tables
14
+ """
12
15
  if isinstance(directory, str):
13
16
  directory = Path(directory)
14
17
  self.__directory = directory
@@ -28,6 +31,7 @@ class DictatureTableDirectory(DictatureTableMock):
28
31
  def __init__(self, path_root: Path, name: str, db_prefix: str, prefix: str = 'item_') -> None:
29
32
  self.__path = path_root / (db_prefix + self._filename_encode(name, suffix=''))
30
33
  self.__prefix = prefix
34
+ self.__serializer = ValueSerializer(mode=ValueSerializerMode.filename_only)
31
35
 
32
36
  def keys(self) -> Iterable[str]:
33
37
  for child in self.__path.iterdir():
@@ -44,8 +48,7 @@ class DictatureTableDirectory(DictatureTableMock):
44
48
  file_target = self.__item_path(item)
45
49
  file_target_tmp = file_target.with_suffix('.tmp')
46
50
 
47
- save_as_json = value.mode != ValueMode.string.value or value.value.startswith('{')
48
- save_data = dumps({'value': value.value, 'mode': value.mode}, indent=1) if save_as_json else value.value
51
+ save_data = self.__serializer.serialize(value)
49
52
 
50
53
  file_target_tmp.write_text(save_data)
51
54
  file_target_tmp.rename(file_target)
@@ -53,10 +56,7 @@ class DictatureTableDirectory(DictatureTableMock):
53
56
  def get(self, item: str) -> Value:
54
57
  try:
55
58
  save_data = self.__item_path(item).read_text()
56
- if save_data.startswith('{'):
57
- data = loads(save_data)
58
- return Value(data['value'], data['mode'])
59
- return Value(save_data, ValueMode.string.value)
59
+ return self.__serializer.deserialize(save_data)
60
60
  except FileNotFoundError:
61
61
  raise KeyError(item)
62
62
 
@@ -69,14 +69,13 @@ class DictatureTableDirectory(DictatureTableMock):
69
69
 
70
70
  @staticmethod
71
71
  def _filename_encode(name: str, suffix: str = '.txt') -> str:
72
- if name == sub(r'[^\w_. -]', '_', name):
73
- return f"d_{name}{suffix}"
74
- name = name.encode('utf-8').hex()
75
- return f'e_{name}{suffix}'
72
+ return ValueSerializer(mode=ValueSerializerMode.filename_only).serialize(Value(
73
+ value=name,
74
+ mode=ValueMode.string.value
75
+ )) + suffix
76
76
 
77
77
  @staticmethod
78
78
  def _filename_decode(name: str, suffix: str = '.txt') -> str:
79
- encoded_name = name[2:-len(suffix) if suffix else len(name)]
80
- if name.startswith('d_'):
81
- return encoded_name
82
- return bytes.fromhex(encoded_name).decode('utf-8')
79
+ if suffix:
80
+ name = name[:-len(suffix)]
81
+ return ValueSerializer(mode=ValueSerializerMode.filename_only).deserialize(name).value
@@ -0,0 +1,109 @@
1
+ from typing import Iterable, Optional
2
+
3
+ from .mock import DictatureTableMock, DictatureBackendMock, Value, ValueMode, ValueSerializer, ValueSerializerMode
4
+
5
+ try:
6
+ from pymisp import PyMISP, MISPEvent, MISPAttribute
7
+ except ImportError as e:
8
+ raise ImportError("Please install the 'pymisp' package to use the 'DictatureBackendMISP' backend.") from e
9
+
10
+
11
+ class DictatureBackendMISP(DictatureBackendMock):
12
+ def __init__(self, misp: PyMISP, tag_name: str = 'storage:dictature', prefix: str = 'Dictature storage: ') -> None:
13
+ """
14
+ Create a new MISP backend
15
+ :param misp: PyMISP instance
16
+ :param tag_name: tag name to use for the tables
17
+ :param prefix: prefix for the event names
18
+ """
19
+ self.__misp = misp
20
+ self.__tag_name = tag_name
21
+ self.__prefix = prefix
22
+
23
+ def keys(self) -> Iterable[str]:
24
+ for event in self.__misp.search(tags=[self.__tag_name], pythonify=True):
25
+ name = event.info
26
+ if not name.startswith(self.__prefix):
27
+ continue
28
+ yield name[len(self.__prefix):]
29
+
30
+ def table(self, name: str) -> 'DictatureTableMock':
31
+ return DictatureTableMISP(self.__misp, self.__prefix + name, self.__tag_name)
32
+
33
+
34
+ class DictatureTableMISP(DictatureTableMock):
35
+ def __init__(self, misp: PyMISP, event_description: str, tag: str) -> None:
36
+ self.__misp = misp
37
+ self.__event_description = event_description
38
+ self.__tag = tag
39
+ self.__event: Optional[MISPEvent] = None
40
+ self.__serializer = ValueSerializer(mode=ValueSerializerMode.ascii_only)
41
+
42
+ def keys(self) -> Iterable[str]:
43
+ for attribute in self.__event_attributes():
44
+ yield attribute.value
45
+
46
+ def drop(self) -> None:
47
+ self.__misp.delete_event(self.__get_event())
48
+
49
+ def create(self) -> None:
50
+ self.__get_event()
51
+
52
+ def set(self, item: str, value: Value) -> None:
53
+ item_name = self.__serializer.serialize(Value(value=item, mode=ValueMode.string.value))
54
+ save_data = self.__serializer.serialize(value)
55
+
56
+ for attribute in self.__event_attributes():
57
+ if attribute.value == item_name:
58
+ attribute.value = item_name
59
+ attribute.comment = save_data
60
+ self.__misp.update_attribute(attribute)
61
+ break
62
+ else:
63
+ attribute = MISPAttribute()
64
+ attribute.value = item_name
65
+ attribute.comment = save_data
66
+ attribute.type = 'comment'
67
+ attribute.to_ids = False
68
+ attribute.disable_correlation = True
69
+ self.__misp.add_attribute(self.__get_event(), attribute)
70
+ self.__get_event().attributes.append(attribute)
71
+
72
+ def get(self, item: str) -> Value:
73
+ item_name = self.__serializer.serialize(Value(value=item, mode=ValueMode.string.value))
74
+ for attribute in self.__event_attributes():
75
+ if attribute.value == item_name:
76
+ return self.__serializer.deserialize(attribute.comment)
77
+ raise KeyError(item)
78
+
79
+ def delete(self, item: str) -> None:
80
+ for attribute in self.__event_attributes():
81
+ if attribute.value == item:
82
+ # First update the attribute as deletion is not recognized immediately
83
+ attribute.type = 'other'
84
+ self.__misp.update_attribute(attribute)
85
+ self.__misp.delete_attribute(attribute)
86
+ break
87
+
88
+ def __get_event(self) -> MISPEvent:
89
+ if self.__event is None:
90
+ for event in self.__misp.search(tags=[self.__tag], eventinfo=self.__event_description, pythonify=True):
91
+ if event.info == self.__event_description:
92
+ self.__event = event
93
+ break
94
+ else:
95
+ event = MISPEvent()
96
+ event.info = self.__event_description
97
+ event.distribution = 0
98
+ event.threat_level_id = 4
99
+ event.analysis = 0
100
+ event.add_tag(self.__tag)
101
+ self.__misp.add_event(event)
102
+ self.__event = event
103
+ return self.__event
104
+
105
+ def __event_attributes(self) -> Iterable[MISPAttribute]:
106
+ for attribute in self.__get_event().attributes:
107
+ if attribute.type != 'comment' or (hasattr(attribute, 'deleted') and attribute.deleted):
108
+ continue
109
+ yield attribute
dictature/backend/mock.py CHANGED
@@ -1,3 +1,5 @@
1
+ from json import dumps, loads
2
+ from string import hexdigits, ascii_letters, digits, printable
1
3
  from typing import Iterable, NamedTuple
2
4
  from enum import Enum
3
5
 
@@ -13,29 +15,145 @@ class Value(NamedTuple):
13
15
  mode: int
14
16
 
15
17
 
18
+ class ValueSerializerMode(Enum):
19
+ any_string = 0
20
+ ascii_only = 1
21
+ filename_only = 2
22
+ hex_only = 3
23
+
24
+
25
+ class ValueSerializer:
26
+ prefix = 'a09e'
27
+
28
+ def __init__(self, mode: ValueSerializerMode = ValueSerializerMode.any_string):
29
+ self.__mode = mode
30
+
31
+ def deserialize(self, serialized: str) -> Value:
32
+ """
33
+ Deserialize a string into a Value object
34
+ :param serialized: serialized string
35
+ :return: Value object
36
+ """
37
+ # Check if the string starts with the prefix (hex-encoded)
38
+ if serialized.startswith(self.prefix):
39
+ # Decode the hex part
40
+ hex_data = serialized[len(self.prefix):]
41
+ decoded = bytes.fromhex(hex_data).decode('ascii')
42
+ # Recursively deserialize the decoded string
43
+ return ValueSerializer(mode=ValueSerializerMode.ascii_only).deserialize(decoded)
44
+
45
+ # Check if the string looks like JSON
46
+ if serialized.startswith('{'):
47
+ try:
48
+ data = loads(serialized)
49
+ return Value(value=data['value'], mode=data['mode'])
50
+ except (ValueError, KeyError):
51
+ pass # Not valid JSON or missing required keys
52
+
53
+ # Direct value (string mode)
54
+ return Value(value=serialized, mode=ValueMode.string.value)
55
+
56
+ def serialize(self, value: Value) -> str:
57
+ """
58
+ Serializes a `Value` object into a `str`, converting its data representation
59
+ based on the serialization mode set in `ValueSerializerMode`. Depending on
60
+ the mode provided, the object can be serialized directly if its string
61
+ representation conforms to certain criteria, or it may be converted into
62
+ a JSON format. Handles customization of allowed characters and uses prefix
63
+ to encode conditions if the direct representation is not permitted.
64
+
65
+ If the mode is incompatible or unsupported, raises a `NotImplementedError`.
66
+
67
+ :param value: Instance of `Value` to be serialized.
68
+
69
+ :return: Serialized representation of the `Value` object as a string.
70
+
71
+ :raises NotImplementedError: If the mode provided in `ValueSerializerMode`
72
+ is unsupported.
73
+ """
74
+ if self.__mode == ValueSerializerMode.hex_only:
75
+ allowed_chars = hexdigits
76
+ elif self.__mode == ValueSerializerMode.filename_only:
77
+ allowed_chars = ascii_letters + digits + '_.'
78
+ elif self.__mode in (ValueSerializerMode.any_string, ValueSerializerMode.ascii_only):
79
+ allowed_chars = None
80
+ else:
81
+ raise NotImplementedError(self.__mode)
82
+
83
+ if allowed_chars is not None:
84
+ # Only a subset of characters is allowed if all match and do not start with the reserved prefix, encode directly
85
+ if value.mode == ValueMode.string.value and all(map(lambda x: x in allowed_chars, value.value)) and not value.value.startswith(self.prefix):
86
+ return value.value
87
+ return self.prefix + ValueSerializer(mode=ValueSerializerMode.ascii_only).serialize(value).encode('ascii').hex()
88
+
89
+ # Save as JSON if not string or value is starting with { (indicating JSON)
90
+ save_as_json = value.mode != ValueMode.string.value or value.value.startswith('{') or value.value.startswith(self.prefix)
91
+ # Save as JSON if only ASCII strings are allowed
92
+ save_as_json = save_as_json or (self.__mode == ValueSerializerMode.ascii_only and any(filter(lambda x: x not in printable, value.value)))
93
+ return dumps({'value': value.value, 'mode': value.mode}, indent=1) if save_as_json else value.value
94
+
95
+
16
96
  class DictatureBackendMock:
17
97
  def keys(self) -> Iterable[str]:
98
+ """
99
+ Return all table names
100
+ :return: all table names
101
+ """
18
102
  raise NotImplementedError("This method should be implemented by the subclass")
19
103
 
20
104
  def table(self, name: str) -> 'DictatureTableMock':
105
+ """
106
+ Create a table object based on the name
107
+ :param name: name of the table
108
+ :return: table object
109
+ """
21
110
  raise NotImplementedError("This method should be implemented by the subclass")
22
111
 
23
112
 
24
113
  class DictatureTableMock:
25
114
  def keys(self) -> Iterable[str]:
115
+ """
116
+ Return all keys in the table
117
+ :return: all keys in the table
118
+ """
26
119
  raise NotImplementedError("This method should be implemented by the subclass")
27
120
 
28
121
  def drop(self) -> None:
122
+ """
123
+ Delete the table
124
+ :return: None
125
+ """
29
126
  raise NotImplementedError("This method should be implemented by the subclass")
30
127
 
31
128
  def create(self) -> None:
129
+ """
130
+ Create the table in the backend
131
+ :return: None
132
+ """
32
133
  raise NotImplementedError("This method should be implemented by the subclass")
33
134
 
34
135
  def set(self, item: str, value: Value) -> None:
136
+ """
137
+ Set a value in the table
138
+ :param item: key to set
139
+ :param value: value to set
140
+ :return: None
141
+ """
35
142
  raise NotImplementedError("This method should be implemented by the subclass")
36
143
 
37
144
  def get(self, item: str) -> Value:
145
+ """
146
+ Get a value from the table
147
+ :param item: key to get
148
+ :return: value
149
+ :raises KeyError: if the key does not exist
150
+ """
38
151
  raise NotImplementedError("This method should be implemented by the subclass")
39
152
 
40
153
  def delete(self, item: str) -> None:
41
- raise NotImplementedError("This method should be implemented by the subclass")
154
+ """
155
+ Delete a value from the table
156
+ :param item: key to delete
157
+ :return: None
158
+ """
159
+ raise NotImplementedError("This method should be implemented by the subclass")
@@ -7,6 +7,10 @@ from .mock import DictatureBackendMock, DictatureTableMock, Value, ValueMode
7
7
 
8
8
  class DictatureBackendSQLite(DictatureBackendMock):
9
9
  def __init__(self, file: Union[str, Path]) -> None:
10
+ """
11
+ Create a new SQLite backend
12
+ :param file: file to store the database
13
+ """
10
14
  if isinstance(file, str):
11
15
  file = Path(file)
12
16
  self.__file = file
@@ -33,7 +37,6 @@ class DictatureBackendSQLite(DictatureBackendMock):
33
37
  self.__connection.close()
34
38
 
35
39
 
36
-
37
40
  class DictatureTableSQLite(DictatureTableMock):
38
41
  def __init__(self, parent: "DictatureBackendSQLite", name: str) -> None:
39
42
  self.__parent = parent
dictature/dictature.py CHANGED
@@ -17,30 +17,63 @@ class Dictature:
17
17
  value_transformer: MockTransformer = PassthroughTransformer(),
18
18
  table_name_transformer: Optional[MockTransformer] = None,
19
19
  ) -> None:
20
+ """
21
+ Create a new Dictature object
22
+ :param backend: backend to use
23
+ :param name_transformer: transformer to use for table and key names
24
+ :param value_transformer: transformer to use for values
25
+ :param table_name_transformer: transformer to use for table names, if None, name_transformer is used
26
+ """
20
27
  self.__backend = backend
21
28
  self.__table_cache: Dict[str, "DictatureTable"] = {}
22
29
  self.__name_transformer = name_transformer
23
30
  self.__value_transformer = value_transformer
24
31
  self.__table_name_transformer = table_name_transformer or name_transformer
32
+ self.__cache_size = 4096
25
33
 
26
34
  def keys(self) -> Set[str]:
35
+ """
36
+ Return all table names
37
+ :return: all table names
38
+ """
27
39
  return set(map(self.__name_transformer.backward, self.__backend.keys()))
28
40
 
29
41
  def values(self) -> Iterator["DictatureTable"]:
42
+ """
43
+ Return all tables
44
+ :return: all tables
45
+ """
30
46
  return map(lambda x: x[1], self.items())
31
47
 
32
48
  def items(self) -> Iterator[Tuple[str, "DictatureTable"]]:
49
+ """
50
+ Return all tables with their instances
51
+ :return: all tables with their instances
52
+ """
33
53
  for k in self.keys():
34
54
  yield k, self[k]
35
55
 
36
56
  def to_dict(self) -> Dict[str, Any]:
57
+ """
58
+ Return all tables as a dictionary
59
+ :return: all tables as a dictionary
60
+ """
37
61
  return {k: v.to_dict() for k, v in self.items()}
38
62
 
39
63
  def __str__(self):
64
+ """
65
+ Return all tables as a string
66
+ :return: all tables as a string
67
+ """
40
68
  return str(self.to_dict())
41
69
 
42
70
  def __getitem__(self, item: str) -> "DictatureTable":
43
- if len(self.__table_cache) > 128:
71
+ """
72
+ Get a table by name
73
+ :param item: name of the table
74
+ :return: table instance
75
+ """
76
+ if len(self.__table_cache) > self.__cache_size:
44
77
  del self.__table_cache[choice(list(self.__table_cache.keys()))]
45
78
  if item not in self.__table_cache:
46
79
  self.__table_cache[item] = DictatureTable(
@@ -52,12 +85,26 @@ class Dictature:
52
85
  return self.__table_cache[item]
53
86
 
54
87
  def __delitem__(self, key: str) -> None:
88
+ """
89
+ Delete a table
90
+ :param key: name of the table
91
+ :return: None
92
+ """
55
93
  self[key].drop()
56
94
 
57
95
  def __contains__(self, item: str) -> bool:
96
+ """
97
+ Check if a table exists
98
+ :param item: name of the table
99
+ :return: True if the table exists, False otherwise
100
+ """
58
101
  return item in self.keys()
59
102
 
60
103
  def __bool__(self) -> bool:
104
+ """
105
+ Check if there are any tables
106
+ :return: True if there are tables, False otherwise
107
+ """
61
108
  return not not self.keys()
62
109
 
63
110
 
@@ -69,6 +116,13 @@ class DictatureTable:
69
116
  name_transformer: MockTransformer = PassthroughTransformer(),
70
117
  value_transformer: MockTransformer = PassthroughTransformer()
71
118
  ):
119
+ """
120
+ Create a new DictatureTable object
121
+ :param backend: backend to use
122
+ :param table_name: name of the table
123
+ :param name_transformer: transformer to use for key names
124
+ :param value_transformer: transformer to use for values
125
+ """
72
126
  self.__backend = backend
73
127
  self.__name_transformer = name_transformer
74
128
  self.__value_transformer = value_transformer
@@ -76,37 +130,77 @@ class DictatureTable:
76
130
  self.__table_created = False
77
131
 
78
132
  def get(self, item: str, default: Optional[Any] = None) -> Any:
133
+ """
134
+ Get a value from the table
135
+ :param item: key to get
136
+ :param default: default value to return if the key does not exist
137
+ :return: value or default
138
+ """
79
139
  try:
80
140
  return self[item]
81
141
  except KeyError:
82
142
  return default
83
143
 
84
144
  def key_exists(self, item: str) -> bool:
145
+ """
146
+ Check if a key exists
147
+ :param item: key to check
148
+ :return: True if the key exists, False otherwise
149
+ """
85
150
  self.__create_table()
86
151
  return item in self.keys()
87
152
 
88
153
  def keys(self) -> Set[str]:
154
+ """
155
+ Return all keys in the table
156
+ :return: all keys in the table
157
+ """
89
158
  self.__create_table()
90
159
  return set(map(self.__name_transformer.backward, self.__table.keys()))
91
160
 
92
161
  def values(self) -> Iterator[Any]:
162
+ """
163
+ Return all values in the table
164
+ :return: all values in the table
165
+ """
93
166
  return map(lambda x: x[1], self.items())
94
167
 
95
168
  def items(self) -> Iterator[Tuple[str, Any]]:
169
+ """
170
+ Return all items in the table
171
+ :return: all items in the table
172
+ """
96
173
  for k in self.keys():
97
174
  yield k, self[k]
98
175
 
99
176
  def drop(self) -> None:
177
+ """
178
+ Delete the table
179
+ :return: None
180
+ """
100
181
  self.__create_table()
101
182
  self.__table.drop()
102
183
 
103
184
  def to_dict(self) -> Dict[str, Any]:
185
+ """
186
+ Return all items as a dictionary
187
+ :return: all items as a dictionary
188
+ """
104
189
  return {k: v for k, v in self.items()}
105
190
 
106
191
  def __str__(self):
192
+ """
193
+ Return all items as a string
194
+ :return: all items as a string
195
+ """
107
196
  return str(self.to_dict())
108
197
 
109
198
  def __getitem__(self, item: str) -> Any:
199
+ """
200
+ Get a value from the table
201
+ :param item: key to get
202
+ :return: value
203
+ """
110
204
  self.__create_table()
111
205
  saved_value = self.__table.get(self.__item_key(item))
112
206
  mode = ValueMode(saved_value.mode)
@@ -120,37 +214,66 @@ class DictatureTable:
120
214
  raise ValueError(f"Unknown mode '{mode}'")
121
215
 
122
216
  def __setitem__(self, key: str, value: Any) -> None:
217
+ """
218
+ Set a value in the table
219
+ :param key: key to set
220
+ :param value: value to set
221
+ :return: None
222
+ """
123
223
  self.__create_table()
124
- value_mode = ValueMode.string
224
+ value_mode: int = ValueMode.string.value
125
225
 
126
226
  if type(value) is not str:
127
227
  try:
128
228
  value = json.dumps(value)
129
- value_mode = ValueMode.json
229
+ value_mode = ValueMode.json.value
130
230
  except TypeError:
131
231
  value = b64encode(compress(pickle.dumps(value))).decode('ascii')
132
- value_mode = value_mode.pickle
232
+ value_mode = ValueMode.pickle.value
133
233
 
134
234
  key = self.__item_key(key)
135
235
  value = self.__value_transformer.forward(value)
136
- self.__table.set(key, Value(value=value, mode=value_mode.value))
236
+ self.__table.set(key, Value(value=value, mode=value_mode))
137
237
 
138
238
  def __delitem__(self, key: str) -> None:
239
+ """
240
+ Delete a key from the table
241
+ :param key: key to delete
242
+ :return: None
243
+ """
139
244
  self.__table.delete(self.__item_key(key))
140
245
 
141
246
  def __contains__(self, item: str):
247
+ """
248
+ Check if a key exists
249
+ :param item: key to check
250
+ :return: True if the key exists, False otherwise
251
+ """
142
252
  return item in self.keys()
143
253
 
144
254
  def __bool__(self) -> bool:
255
+ """
256
+ Check if there are any items in the table
257
+ :return: True if there are items, False otherwise
258
+ """
145
259
  return not not self.keys()
146
260
 
147
261
  def __create_table(self) -> None:
262
+ """
263
+ Create the table if it does not exist
264
+ :return: None
265
+ """
148
266
  if self.__table_created:
149
267
  return
150
268
  self.__table.create()
151
269
  self.__table_created = True
152
270
 
153
271
  def __item_key(self, item: str) -> str:
272
+ """
273
+ Transform the key for storage
274
+ :param item: key to transform
275
+ :return: transformed key
276
+ """
154
277
  if not self.__name_transformer.static:
155
278
  for key in self.__table.keys():
156
279
  if self.__name_transformer.backward(key) == item:
@@ -158,6 +281,11 @@ class DictatureTable:
158
281
  return self.__name_transformer.forward(item)
159
282
 
160
283
  def __table_key(self, table_name: str) -> str:
284
+ """
285
+ Transform the table name for storage
286
+ :param table_name: table name to transform
287
+ :return: transformed table name
288
+ """
161
289
  if not self.__name_transformer.static:
162
290
  for key in self.__backend.keys():
163
291
  if self.__name_transformer.backward(key) == table_name:
@@ -10,6 +10,12 @@ from .mock import MockTransformer
10
10
 
11
11
  class AESTransformer(MockTransformer):
12
12
  def __init__(self, passphrase: str, static_names_mode: bool, salt: str = 'dictature') -> None:
13
+ """
14
+ Create a new AES transformer
15
+ :param passphrase: secret passphrase to encrypt/decrypt the data
16
+ :param static_names_mode: if True, the transformer will use ECB mode instead of GCM (True decreases security, increases speed)
17
+ :param salt: salt to use for the key derivation
18
+ """
13
19
  self.__key = scrypt(passphrase, salt, 16, N=2 ** 14, r=8, p=1)
14
20
  self.__mode = AES.MODE_GCM if not static_names_mode else AES.MODE_ECB
15
21
  self.__static = static_names_mode
@@ -5,6 +5,10 @@ from .mock import MockTransformer
5
5
 
6
6
  class HmacTransformer(MockTransformer):
7
7
  def __init__(self, secret: str = 'dictature') -> None:
8
+ """
9
+ Perform HMAC on the text.
10
+ :param secret: secret key to use for HMAC, if not provided works as a simple hash function
11
+ """
8
12
  self.__secret = secret
9
13
 
10
14
  def forward(self, text: str) -> str:
@@ -1,9 +1,25 @@
1
1
 
2
2
  class MockTransformer:
3
3
  def forward(self, text: str) -> str:
4
+ """
5
+ Transform the text in some way to the data format in data storage
6
+ :param text: text to transform
7
+ :return: transformed text
8
+ """
4
9
  raise NotImplementedError("This method should be implemented by the child class")
10
+
5
11
  def backward(self, text: str) -> str:
12
+ """
13
+ Transform the data format in data storage to the text
14
+ :param text: text to transform
15
+ :return: original text
16
+ """
6
17
  raise NotImplementedError("This method should be implemented by the child class")
18
+
7
19
  @property
8
20
  def static(self) -> bool:
21
+ """
22
+ Returns True only if when the forward transformation is applied to the same text, the result is always the same
23
+ :return: True if the transformation is static
24
+ """
9
25
  raise NotImplementedError("This method should be implemented by the child class")
@@ -1,10 +1,17 @@
1
1
  from .mock import MockTransformer
2
2
 
3
+
3
4
  class PassthroughTransformer(MockTransformer):
5
+ """
6
+ Passthrough transformer, does not modify the text.
7
+ """
8
+
4
9
  def forward(self, text: str) -> str:
5
10
  return text
11
+
6
12
  def backward(self, text: str) -> str:
7
13
  return text
14
+
8
15
  @property
9
16
  def static(self) -> bool:
10
17
  return True
@@ -5,6 +5,10 @@ from .mock import MockTransformer
5
5
 
6
6
  class PipelineTransformer(MockTransformer):
7
7
  def __init__(self, transformers: List[MockTransformer]) -> None:
8
+ """
9
+ Create a pipeline of transformers. The text is passed through each transformer in the order they are provided.
10
+ :param transformers: list of transformers to use
11
+ """
8
12
  self.__transformers = transformers
9
13
 
10
14
  def forward(self, text: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dictature
3
- Version: 0.9.3
3
+ Version: 0.9.5
4
4
  Summary: dictature -- A generic wrapper around dict-like interface with mulitple backends
5
5
  Author-email: Adam Hlavacek <git@adamhlavacek.com>
6
6
  Project-URL: Homepage, https://github.com/esoadamo/dictature
@@ -53,6 +53,7 @@ del dictionary['test'] # drops whole table
53
53
  Currently, the following backends are supported:
54
54
  - `DictatureBackendDirectory`: stores the data in a directory as json files
55
55
  - `DictatureBackendSQLite`: stores the data in a SQLite database
56
+ - `DictatureBackendMISP`: stores the data in a MISP instance
56
57
 
57
58
  ### Transformers
58
59
 
@@ -73,3 +74,9 @@ dictionary = Dictature(
73
74
  value_transformer=value_transformer
74
75
  )
75
76
  ```
77
+
78
+ Currently, the following transformers are supported:
79
+ - `AESTransformer`: encrypts/decrypts the data using AES
80
+ - `HmacTransformer`: signs the data using HMAC or performs hash integrity checks
81
+ - `PassthroughTransformer`: does nothing
82
+ - `PipelineTransformer`: chains multiple transformers
@@ -0,0 +1,18 @@
1
+ dictature/__init__.py,sha256=UCPJKHeyirRZ0pCYoyeat-rwXa8pDezOJ3UWCipDdyc,33
2
+ dictature/dictature.py,sha256=SHwG_XvGwm6qpvMt5OjeS8BoU5wLgJi_4jIBKFmYFyI,9330
3
+ dictature/backend/__init__.py,sha256=d5s6QCJOUzFglVNg8Cqqx_8b61S-AOTGjEUIF6FS69U,149
4
+ dictature/backend/directory.py,sha256=pVXzwswxu9I38tiWhpqXmXhFFW57BzW_ScKHanOCPK0,3201
5
+ dictature/backend/misp.py,sha256=iPjvgnJg6WveNP2wvgN7OK2vkX-SC9qYPrdoa9ahRT0,4411
6
+ dictature/backend/mock.py,sha256=BzfLstxkTIjk6mcMTdFKj8rSaFgIqn9-2Cyelslj8bY,5889
7
+ dictature/backend/sqlite.py,sha256=zyphYEeLY4eGuBCor16i80_-brdipMpXZ3_kONwErsE,5237
8
+ dictature/transformer/__init__.py,sha256=JIFJpXU6iB9hIUM8L7HL2o9Nqjm_YbMEuQBQC8ZJ6b4,124
9
+ dictature/transformer/aes.py,sha256=ZhC1dT9QpnziErkDLriWLgXDEFNGQW0KG4aqSN2AZpA,1926
10
+ dictature/transformer/hmac.py,sha256=vURsB0HlzRPn_Vkl7lGmZV9OKempQuds8AanmadDxIo,834
11
+ dictature/transformer/mock.py,sha256=7zu65ZqUV_AVRaPSzNd73cVMXixXt31SeuX9OKZxaJQ,948
12
+ dictature/transformer/passthrough.py,sha256=Pt3N6G_Qh6HJ_q75ETL5nfAwYHLB-SjkVwUwbbbMik8,344
13
+ dictature/transformer/pipeline.py,sha256=OaQaJeJ5NpICetJe08r8ontqstsXGuW8jDbKw1zxYs4,842
14
+ dictature-0.9.5.dist-info/LICENSE,sha256=n1U9DKr8sM5EY2QHcvxSGiKTDWUT8MyXsOC79w94MT0,1072
15
+ dictature-0.9.5.dist-info/METADATA,sha256=1qIyWXXTEogMI-mJ98ra_DCpUhU3soP8UNiV74zA8ek,2826
16
+ dictature-0.9.5.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
17
+ dictature-0.9.5.dist-info/top_level.txt,sha256=-RO39WWCF44lqiXhSUcACVqbk6SkgReZTz7ZmHKH3-U,10
18
+ dictature-0.9.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.0)
2
+ Generator: setuptools (75.3.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,17 +0,0 @@
1
- dictature/__init__.py,sha256=UCPJKHeyirRZ0pCYoyeat-rwXa8pDezOJ3UWCipDdyc,33
2
- dictature/dictature.py,sha256=eFjUc5Q1DUXLNqk-UeHzFqWgCZppTsoK3TXCJ1UuCS8,5666
3
- dictature/backend/__init__.py,sha256=d5s6QCJOUzFglVNg8Cqqx_8b61S-AOTGjEUIF6FS69U,149
4
- dictature/backend/directory.py,sha256=KVbKS1CibXmY1NsZRWuTE0uC4DXRLOXqYEHxyHJidCc,3266
5
- dictature/backend/mock.py,sha256=Qd7KSh-qM763Jc7biDf5xYFWdgDax30dUHh2gXWwTZE,1266
6
- dictature/backend/sqlite.py,sha256=aExNxDtx1kiPrZn-jfCzbpV4alEXyGc6f12tuCJK1tk,5130
7
- dictature/transformer/__init__.py,sha256=JIFJpXU6iB9hIUM8L7HL2o9Nqjm_YbMEuQBQC8ZJ6b4,124
8
- dictature/transformer/aes.py,sha256=6H3jNkUpgWBX88BduMzbi9MDSRxMHnWmZZEIJ70BLi0,1601
9
- dictature/transformer/hmac.py,sha256=pYw6ybUIMoNdU2JFI9ffePr-33ails-CN9J6rFt7RVE,677
10
- dictature/transformer/mock.py,sha256=osETvYZjlgos0trJy0YvXcmtNy0L6x2h2099t1aHMFc,421
11
- dictature/transformer/passthrough.py,sha256=63hZCPQMUJa-G6ZKdv_xt2fMiMZpmPoL84PY5eb2ueE,269
12
- dictature/transformer/pipeline.py,sha256=-2r9FxLXEnk3qpCfXC0qp0KqNC2qkpChCJYEbZAQRYM,642
13
- dictature-0.9.3.dist-info/LICENSE,sha256=n1U9DKr8sM5EY2QHcvxSGiKTDWUT8MyXsOC79w94MT0,1072
14
- dictature-0.9.3.dist-info/METADATA,sha256=BPA99McqtwhQxjALGLwrWvaZiqRx3XHnjYZ5tEfxYIk,2478
15
- dictature-0.9.3.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
16
- dictature-0.9.3.dist-info/top_level.txt,sha256=-RO39WWCF44lqiXhSUcACVqbk6SkgReZTz7ZmHKH3-U,10
17
- dictature-0.9.3.dist-info/RECORD,,