|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to configure safe browsing support. |
|
8 """ |
|
9 |
|
10 from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QDateTime |
|
11 from PyQt5.QtWidgets import ( |
|
12 QDialog, QDialogButtonBox, QAbstractButton |
|
13 ) |
|
14 |
|
15 from E5Gui import E5MessageBox |
|
16 from E5Gui.E5OverrideCursor import E5OverrideCursor |
|
17 |
|
18 from .Ui_SafeBrowsingDialog import Ui_SafeBrowsingDialog |
|
19 |
|
20 import UI.PixmapCache |
|
21 import Preferences |
|
22 |
|
23 |
|
24 class SafeBrowsingDialog(QDialog, Ui_SafeBrowsingDialog): |
|
25 """ |
|
26 Class implementing a dialog to configure safe browsing support. |
|
27 """ |
|
28 def __init__(self, manager, parent=None): |
|
29 """ |
|
30 Constructor |
|
31 |
|
32 @param manager reference to the safe browsing manager |
|
33 @type SafeBrowsingManager |
|
34 @param parent reference to the parent widget |
|
35 @type QWidget |
|
36 """ |
|
37 super().__init__(parent) |
|
38 self.setupUi(self) |
|
39 self.setWindowFlags(Qt.WindowType.Window) |
|
40 |
|
41 self.__manager = manager |
|
42 self.__manager.progressMessage.connect(self.__setProgressMessage) |
|
43 self.__manager.progress.connect(self.__setProgress) |
|
44 |
|
45 self.iconLabel.setPixmap( |
|
46 UI.PixmapCache.getPixmap("safeBrowsing48")) |
|
47 |
|
48 self.__gsbHelpDialog = None |
|
49 |
|
50 self.__enabled = Preferences.getWebBrowser("SafeBrowsingEnabled") |
|
51 self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey") |
|
52 self.__filterPlatform = Preferences.getWebBrowser( |
|
53 "SafeBrowsingFilterPlatform") |
|
54 self.__automaticUpdate = Preferences.getWebBrowser( |
|
55 "SafeBrowsingAutoUpdate") |
|
56 self.__useLookupApi = Preferences.getWebBrowser( |
|
57 "SafeBrowsingUseLookupApi") |
|
58 |
|
59 self.buttonBox.setFocus() |
|
60 |
|
61 msh = self.minimumSizeHint() |
|
62 self.resize(max(self.width(), msh.width()), msh.height()) |
|
63 |
|
64 def show(self): |
|
65 """ |
|
66 Public slot to show the dialog. |
|
67 """ |
|
68 self.gsbGroupBox.setChecked(self.__enabled) |
|
69 self.gsbApiKeyEdit.setText(self.__apiKey) |
|
70 self.gsbFilterPlatformCheckBox.setChecked(self.__filterPlatform) |
|
71 self.gsbAutoUpdateCheckBox.setChecked(self.__automaticUpdate) |
|
72 self.gsbLookupCheckBox.setChecked(self.__useLookupApi) |
|
73 |
|
74 self.__updateCacheButtons() |
|
75 |
|
76 super().show() |
|
77 |
|
78 @pyqtSlot() |
|
79 def on_gsbHelpButton_clicked(self): |
|
80 """ |
|
81 Private slot to show some help text "How to create a safe |
|
82 browsing API key.". |
|
83 """ |
|
84 if self.__gsbHelpDialog is None: |
|
85 from E5Gui.E5SimpleHelpDialog import E5SimpleHelpDialog |
|
86 from . import SafeBrowsingHelp |
|
87 |
|
88 helpStr = SafeBrowsingHelp() |
|
89 self.__gsbHelpDialog = E5SimpleHelpDialog( |
|
90 title=self.tr("Google Safe Browsing API Help"), |
|
91 helpStr=helpStr, parent=self) |
|
92 |
|
93 self.__gsbHelpDialog.show() |
|
94 |
|
95 @pyqtSlot(QAbstractButton) |
|
96 def on_buttonBox_clicked(self, button): |
|
97 """ |
|
98 Private slot called by a button of the button box clicked. |
|
99 |
|
100 @param button button that was clicked (QAbstractButton) |
|
101 """ |
|
102 if button == self.buttonBox.button( |
|
103 QDialogButtonBox.StandardButton.Close |
|
104 ): |
|
105 self.close() |
|
106 |
|
107 @pyqtSlot() |
|
108 def __save(self): |
|
109 """ |
|
110 Private slot to save the configuration. |
|
111 |
|
112 @return flag indicating success |
|
113 @rtype bool |
|
114 """ |
|
115 self.__enabled = self.gsbGroupBox.isChecked() |
|
116 self.__apiKey = self.gsbApiKeyEdit.text() |
|
117 self.__filterPlatform = self.gsbFilterPlatformCheckBox.isChecked() |
|
118 self.__automaticUpdate = self.gsbAutoUpdateCheckBox.isChecked() |
|
119 self.__useLookupApi = self.gsbLookupCheckBox.isChecked() |
|
120 |
|
121 Preferences.setWebBrowser("SafeBrowsingEnabled", self.__enabled) |
|
122 Preferences.setWebBrowser("SafeBrowsingApiKey", self.__apiKey) |
|
123 Preferences.setWebBrowser("SafeBrowsingFilterPlatform", |
|
124 self.__filterPlatform) |
|
125 Preferences.setWebBrowser("SafeBrowsingAutoUpdate", |
|
126 self.__automaticUpdate) |
|
127 Preferences.setWebBrowser("SafeBrowsingUseLookupApi", |
|
128 self.__useLookupApi) |
|
129 |
|
130 self.__manager.configurationChanged() |
|
131 |
|
132 self.__updateCacheButtons() |
|
133 |
|
134 return True |
|
135 |
|
136 def closeEvent(self, evt): |
|
137 """ |
|
138 Protected method to handle close events. |
|
139 |
|
140 @param evt reference to the close event |
|
141 @type QCloseEvent |
|
142 """ |
|
143 if self.__okToClose(): |
|
144 evt.accept() |
|
145 else: |
|
146 evt.ignore() |
|
147 |
|
148 def __isModified(self): |
|
149 """ |
|
150 Private method to check, if the dialog contains modified data. |
|
151 |
|
152 @return flag indicating the presence of modified data |
|
153 @rtype bool |
|
154 """ |
|
155 return ( |
|
156 (self.__enabled != self.gsbGroupBox.isChecked()) or |
|
157 (self.__apiKey != self.gsbApiKeyEdit.text()) or |
|
158 (self.__filterPlatform != |
|
159 self.gsbFilterPlatformCheckBox.isChecked()) or |
|
160 (self.__automaticUpdate != |
|
161 self.gsbAutoUpdateCheckBox.isChecked()) or |
|
162 (self.__useLookupApi != self.gsbLookupCheckBox.isChecked()) |
|
163 ) |
|
164 |
|
165 def __okToClose(self): |
|
166 """ |
|
167 Private method to check, if it is safe to close the dialog. |
|
168 |
|
169 @return flag indicating safe to close |
|
170 @rtype bool |
|
171 """ |
|
172 if self.__isModified(): |
|
173 res = E5MessageBox.okToClearData( |
|
174 self, |
|
175 self.tr("Safe Browsing Management"), |
|
176 self.tr("""The dialog contains unsaved changes."""), |
|
177 self.__save) |
|
178 if not res: |
|
179 return False |
|
180 return True |
|
181 |
|
182 def __updateCacheButtons(self): |
|
183 """ |
|
184 Private method to set enabled state of the cache buttons. |
|
185 """ |
|
186 enable = self.__enabled and bool(self.__apiKey) |
|
187 |
|
188 self.updateCacheButton.setEnabled(enable) |
|
189 self.clearCacheButton.setEnabled(enable) |
|
190 |
|
191 self.showUpdateTimeButton.setEnabled(enable and self.__automaticUpdate) |
|
192 |
|
193 @pyqtSlot() |
|
194 def on_updateCacheButton_clicked(self): |
|
195 """ |
|
196 Private slot to update the local cache database. |
|
197 """ |
|
198 E5MessageBox.information( |
|
199 self, |
|
200 self.tr("Update Safe Browsing Cache"), |
|
201 self.tr("""Updating the Safe Browsing cache might be a lengthy""" |
|
202 """ operation. Please be patient!""")) |
|
203 |
|
204 with E5OverrideCursor(): |
|
205 ok, error = self.__manager.updateHashPrefixCache() |
|
206 self.__resetProgress() |
|
207 if not ok: |
|
208 if error: |
|
209 E5MessageBox.critical( |
|
210 self, |
|
211 self.tr("Update Safe Browsing Cache"), |
|
212 self.tr("""<p>Updating the Safe Browsing cache failed.""" |
|
213 """</p><p>Reason: {0}</p>""").format(error)) |
|
214 else: |
|
215 E5MessageBox.critical( |
|
216 self, |
|
217 self.tr("Update Safe Browsing Cache"), |
|
218 self.tr("""<p>Updating the Safe Browsing cache failed.""" |
|
219 """</p>""")) |
|
220 |
|
221 @pyqtSlot() |
|
222 def on_clearCacheButton_clicked(self): |
|
223 """ |
|
224 Private slot to clear the local cache database. |
|
225 """ |
|
226 res = E5MessageBox.yesNo( |
|
227 self, |
|
228 self.tr("Clear Safe Browsing Cache"), |
|
229 self.tr("""Do you really want to clear the Safe Browsing cache?""" |
|
230 """ Re-populating it might take some time.""")) |
|
231 if res: |
|
232 with E5OverrideCursor(): |
|
233 self.__manager.fullCacheCleanup() |
|
234 |
|
235 @pyqtSlot(str, int) |
|
236 def __setProgressMessage(self, message, maximum): |
|
237 """ |
|
238 Private slot to set the progress message and the maximum value. |
|
239 |
|
240 @param message progress message to be set |
|
241 @type str |
|
242 @param maximum maximum value to be set |
|
243 @type int |
|
244 """ |
|
245 self.progressLabel.setText(message) |
|
246 self.progressBar.setMaximum(maximum) |
|
247 self.progressBar.setValue(0) |
|
248 |
|
249 @pyqtSlot(int) |
|
250 def __setProgress(self, value): |
|
251 """ |
|
252 Private slot to set the progress value. |
|
253 |
|
254 @param value progress value to be set |
|
255 @type int |
|
256 """ |
|
257 if bool(self.progressLabel.text()): |
|
258 self.progressBar.setValue(value) |
|
259 |
|
260 def __resetProgress(self): |
|
261 """ |
|
262 Private method to reset the progress info. |
|
263 """ |
|
264 self.progressLabel.clear() |
|
265 self.progressBar.setMaximum(100) |
|
266 self.progressBar.setValue(0) |
|
267 |
|
268 @pyqtSlot(str) |
|
269 def on_urlEdit_textChanged(self, text): |
|
270 """ |
|
271 Private slot to handle changes of the entered URL text. |
|
272 |
|
273 @param text entered URL text |
|
274 @type str |
|
275 """ |
|
276 url = QUrl.fromUserInput(text) |
|
277 enable = ( |
|
278 url.isValid() and |
|
279 bool(url.scheme()) and |
|
280 url.scheme() not in self.__manager.getIgnoreSchemes() |
|
281 ) |
|
282 self.urlCheckButton.setEnabled(enable) |
|
283 |
|
284 @pyqtSlot() |
|
285 def on_urlCheckButton_clicked(self): |
|
286 """ |
|
287 Private slot to check the entered URL. |
|
288 """ |
|
289 # Malicious URL for testing: |
|
290 # http://malware.testing.google.test/testing/malware/* |
|
291 # http://ianfette.org |
|
292 # |
|
293 urlStr = self.urlEdit.text() |
|
294 url = QUrl.fromUserInput(urlStr) |
|
295 threatLists, error = self.__manager.lookupUrl(url) |
|
296 |
|
297 if error: |
|
298 E5MessageBox.warning( |
|
299 self, |
|
300 self.tr("Check URL"), |
|
301 self.tr("<p>The Google Safe Browsing Server reported an" |
|
302 " error.</p><p>{0}</p>").format(error) |
|
303 ) |
|
304 elif threatLists: |
|
305 threatMessages = self.__manager.getThreatMessages(threatLists) |
|
306 E5MessageBox.warning( |
|
307 self, |
|
308 self.tr("Check URL"), |
|
309 self.tr("<p>The URL <b>{0}</b> was found in the Safe" |
|
310 " Browsing Database.</p>{1}").format( |
|
311 urlStr, "".join(threatMessages)) |
|
312 ) |
|
313 else: |
|
314 E5MessageBox.information( |
|
315 self, |
|
316 self.tr("Check URL"), |
|
317 self.tr("<p>The URL <b>{0}</b> was not found in the Safe" |
|
318 " Browsing Database and may be considered safe." |
|
319 "</p>") |
|
320 .format(urlStr) |
|
321 ) |
|
322 |
|
323 @pyqtSlot() |
|
324 def on_saveButton_clicked(self): |
|
325 """ |
|
326 Private slot to save the configuration data. |
|
327 """ |
|
328 self.__save() |
|
329 |
|
330 @pyqtSlot() |
|
331 def on_showUpdateTimeButton_clicked(self): |
|
332 """ |
|
333 Private slot to show the time of the next automatic threat list update. |
|
334 """ |
|
335 nextUpdateDateTime = Preferences.getWebBrowser( |
|
336 "SafeBrowsingUpdateDateTime") |
|
337 message = ( |
|
338 self.tr("The next automatic threat list update will be done now.") |
|
339 if (not nextUpdateDateTime.isValid() or |
|
340 nextUpdateDateTime <= QDateTime.currentDateTime()) else |
|
341 self.tr("<p>The next automatic threat list update will be done at" |
|
342 " <b>{0}</b>.</p>").format( |
|
343 nextUpdateDateTime.toString("yyyy-MM-dd, HH:mm:ss")) |
|
344 ) |
|
345 |
|
346 E5MessageBox.information( |
|
347 self, |
|
348 self.tr("Update Time"), |
|
349 message) |