1
- from PyQt5 .QtCore import QObject
2
-
3
1
import json
4
2
import os
5
3
import glob
6
4
import sys
7
5
8
- from opensnitch .utils .xdg import xdg_config_home
9
- from opensnitch .actions import highlight
10
- from opensnitch .actions .default_configs import commonDelegateConfig , rulesDelegateConfig , fwDelegateConfig
6
+ from PyQt5 .QtCore import QObject
11
7
12
- class Actions (QObject ):
13
- """List of actions to perform on the data that is displayed on the GUI.
14
- Whenever an item matches a condition an action is applied, for example:
15
- - if the text of a cell matches a condition for the given columns,
16
- then the properties of the cell/row and the text are customized.
8
+ from opensnitch .utils .xdg import xdg_config_home
9
+ from opensnitch .actions .default_configs import (
10
+ commonDelegateConfig ,
11
+ rulesDelegateConfig ,
12
+ fwDelegateConfig
13
+ )
17
14
18
- There's only 1 action supported right now:
19
- - highlight: for customizing rows and cells appearance.
15
+ from opensnitch . plugins import PluginsList
16
+ from opensnitch . plugins import PluginsManager
20
17
21
- There're 3 actions by default of type Highlight:
22
- - rules: applied to the rules to colorize the columns Enabled and
23
- Action
24
- - firewall: applied to the fw rules to colorize the columns Action and
25
- Target.
26
- - common: applied to the rest of the views to colorize the column
27
- Action.
18
+ class Actions (QObject ):
19
+ """List of actions to perform on the data that is displayed on the GUI,
20
+ defined in JSON files.
28
21
29
- Users can modify the default actions, by adding more patterns to colorize.
30
- At the same time they can also create new actions to be applied on certain views.
22
+ Whenever an item (popup, cell, etc) matches a condition, an action
23
+ (config/plugin) is applied. For example:
24
+ - if the text of a cell matches a condition for the given columns,
25
+ then the properties of the cell/row and the text are colorized.
26
+ - if the result of an analysis of a domain is malicious, colorize
27
+ popups' text labels in red + add a tab with the result of the analysis.
31
28
32
- The format of the actions is JSON:
29
+ The actions are defined in JSON format :
33
30
{
34
31
"created": "....",
35
32
"name": "...",
33
+ "type": ["views"],
36
34
"actions": {
37
35
"highlight": {
38
36
"cells": [
39
- {
40
- "text": ["allow", "True", "online"],
41
- "cols": [3,5,6],
42
- "color": "green",
43
- },
44
- {
45
- "text": ["deny", "False", "offline"],
46
- "cols": [3,5,6],
47
- "color": "red",
48
- }
49
- ],
37
+ {
38
+ "text": ["allow", "True", "online"],
39
+ "cols": [3,5,6],
40
+ "color": "green",
41
+ },
42
+ {
43
+ "text": ["deny", "False", "offline"],
44
+ "cols": [3,5,6],
45
+ "color": "red",
46
+ }
47
+ ],
50
48
"rows": []
51
49
}
52
50
}
53
51
52
+ "type" field is the area of the GUI where the actions will be applied:
53
+
54
+ "global" -> global actions, like background tasks.
55
+ "views" -> applies to Views (lists of items), QItemDelegate
56
+ "popups" -> applies to popups.
57
+ "main-dialog" -> applies to the main window.
58
+ "proc-dialog" -> applies to the Process dialog.
59
+ "procs-list" -> applies to the Procs view. (TODO)
60
+ "domains-list" -> applies to the Domains view. (TODO)
61
+ "ips-list" -> applies to the IPs view. (TODO)
62
+ "db" -> applies to the DB. Modify items before inserting, react to
63
+ data being added, etc. (TODO)
64
+
65
+ "actions" is the list of actions to execute:
66
+
67
+ - the name of the action defines the python plugin to load:
68
+ "highligh" -> plugins/highligh/highlight.py
69
+ "downloader" -> plugins/downloader/downloader.py, etc.
70
+ - every action has its own plugin (*.py file) which is in charge
71
+ of parse and compile to configuration if needed.
72
+ For example for "highlight" action, "color": "red" is compiled
73
+ to QtColor("red")
74
+
75
+ There're 3 hardcoded actions by default of type Highlight:
76
+ - rules: applied to the rules to colorize the columns Enabled and
77
+ Action
78
+ - firewall: applied to the fw rules to colorize the columns Action and
79
+ Target.
80
+ - common: applied to the rest of the views to colorize the column
81
+ Action.
82
+
83
+ Users can modify the default actions, by adding more patterns to colorize,
84
+ and saving them to $XDG_CONFIG_HOME/opensnitch/actions/myaction.json
85
+
86
+ At the same time they can also create new actions in that directory
87
+ to be applied on certain views.
88
+
54
89
"""
55
90
__instance = None
56
91
57
92
# list of loaded actions
58
- _actions = None
93
+ _actions_list = {}
94
+ _plugins = []
59
95
60
96
61
97
KEY_ACTIONS = "actions"
@@ -67,7 +103,7 @@ class Actions(QObject):
67
103
68
104
# default paths to look for actions
69
105
_paths = [
70
- os .path .dirname (sys .modules [__name__ ].__file__ ) + "/data/" ,
106
+ # os.path.dirname(sys.modules[__name__].__file__) + "/data/",
71
107
"{0}/{1}" .format (xdg_config_home , "/opensnitch/actions/" )
72
108
]
73
109
@@ -80,17 +116,33 @@ def instance():
80
116
def __init__ (self , parent = None ):
81
117
QObject .__init__ (self )
82
118
self ._actions_list = {}
119
+ self ._plugin_mgr = PluginsManager .instance ()
83
120
try :
84
121
base_dir = "{0}/{1}" .format (xdg_config_home , "/opensnitch/actions/" )
85
122
os .makedirs (base_dir , 0o700 )
86
- except :
87
- pass
123
+ except Exception as e :
124
+ print ("actions.__init__ exception:" , e )
125
+ #print("ActionsLists:", PluginsList.actions)
88
126
89
127
def _load_default_configs (self ):
128
+ # may be overwritten by user choice
90
129
self ._actions_list [commonDelegateConfig [Actions .KEY_NAME ]] = self .compile (commonDelegateConfig )
91
130
self ._actions_list [rulesDelegateConfig [Actions .KEY_NAME ]] = self .compile (rulesDelegateConfig )
92
131
self ._actions_list [fwDelegateConfig [Actions .KEY_NAME ]] = self .compile (fwDelegateConfig )
93
132
133
+ def load (self , action_file ):
134
+ """read a json file from disk and create the action."""
135
+ try :
136
+ with open (action_file , 'r' ) as fd :
137
+ data = fd .read ()
138
+ obj = json .loads (data )
139
+ action = self .compile (obj )
140
+ return obj , action
141
+ except :
142
+ pass
143
+
144
+ return None , None
145
+
94
146
def loadAll (self ):
95
147
"""look for actions firstly on default system path, secondly on
96
148
user's home.
@@ -103,37 +155,53 @@ def loadAll(self):
103
155
104
156
for path in self ._paths :
105
157
for jfile in glob .glob (os .path .join (path , '*.json' )):
106
- self .load (jfile )
107
-
108
- def load (self , action_file ):
109
- """read a json file from disk and create the action."""
110
- with open (action_file , 'r' ) as fd :
111
- data = fd .read ()
112
- obj = json .loads (data )
113
- self ._actions_list [obj [Actions .KEY_NAME ]] = self .compile (obj )
158
+ #print("Actions.loadconf()", jfile)
159
+ obj , action = self .load (jfile )
160
+ if obj and action :
161
+ self ._actions_list [obj [Actions .KEY_NAME ]] = action
114
162
115
- def compile (self , obj ):
163
+ def compile (self , json_obj ):
164
+ """translates json definitions to python objects"""
116
165
try :
117
- if Actions .KEY_NAME not in obj or obj [Actions .KEY_NAME ] == "" :
166
+ if Actions .KEY_NAME not in json_obj or json_obj [Actions .KEY_NAME ] == "" :
118
167
return None
119
- if obj .get (Actions .KEY_ACTIONS ) == None :
168
+ if json_obj .get (Actions .KEY_ACTIONS ) == None :
120
169
return None
121
170
122
- for action in obj [Actions .KEY_ACTIONS ]:
123
- if action == highlight .Highlight .NAME :
124
- h = highlight .Highlight (obj [Actions .KEY_ACTIONS ][action ])
125
- h .compile ()
126
- obj [Actions .KEY_ACTIONS ][action ]= h
127
- else :
128
- print ("Actions exception: Action '{0}' not supported yet" .format (obj [Actions .KEY_NAME ]))
129
-
130
- return obj
171
+ # "actions": { "highlight": ..., "virustotal": ..., }
172
+ #print("plugins >>", PluginsList.names)
173
+ for action_name in json_obj [Actions .KEY_ACTIONS ]:
174
+ action_obj = json_obj [Actions .KEY_ACTIONS ][action_name ]
175
+ if action_obj == None or action_obj .get ('enabled' ) == None or action_obj .get ('enabled' ) == False :
176
+ print ("actions.compile() skipping disabled action '{0}'" .format (action_name ))
177
+ # FIXME: if one of the action is not enabled, we're
178
+ # invalidating all the configured actions.
179
+ return None
180
+
181
+ # see if the plugin is loaded, if it's not, try to load it.
182
+ if PluginsList .names .get (action_name .capitalize ()) == None :
183
+ if self ._plugin_mgr .load_plugin_byname (action_name , force = True ) == False :
184
+ print ("actions.compile() unable to load plugin name '{0}'" .format (action_name ))
185
+ return None
186
+
187
+ # allow to use "Plugin" or "plugin" to name actions in json
188
+ # files.
189
+ # python class will be always capitalized.
190
+ _p = PluginsList .names .get (action_name .capitalize ())
191
+ plug = _p (action_obj )
192
+
193
+ # compile the plugin, preparing/configuring the plugin
194
+ # before it's used.
195
+ plug .compile ()
196
+
197
+ # save the "compiled" action to the action list
198
+ json_obj [Actions .KEY_ACTIONS ][action_name ]= plug
199
+
200
+ return json_obj
131
201
except Exception as e :
132
202
print ("Actions.compile() exception:" , e )
133
203
return None
134
204
135
-
136
-
137
205
def getAll (self ):
138
206
return self ._actions_list
139
207
@@ -144,7 +212,25 @@ def get(self, name):
144
212
try :
145
213
return self ._actions_list [name ]
146
214
except Exception as e :
147
- print ("get() exception:" , e )
215
+ print ("actions.get() exception:" , e , "name:" , name )
216
+ return None
217
+
218
+ def getByType (self , acttype ):
219
+ try :
220
+ actlist = {}
221
+ for name in self ._actions_list :
222
+ act = self ._actions_list [name ]
223
+ if act == None :
224
+ print ("actions.getByType() none:" , name )
225
+ continue
226
+ types = act .get ('type' )
227
+ if types == None :
228
+ continue
229
+ if acttype in types :
230
+ actlist [name ] = self ._actions_list [name ]
231
+ return actlist
232
+ except Exception as e :
233
+ print ("actions.getByType() ({0}) exception: {1}" .format (acttype , e ))
148
234
return None
149
235
150
236
def delete (self , name ):
0 commit comments