WebBrowser/SafeBrowsing/SafeBrowsingManager.py

branch
safe_browsing
changeset 5829
d3448873ced3
parent 5821
6c7766cde4c1
child 5831
536d97e3f1a1
equal deleted inserted replaced
5821:6c7766cde4c1 5829:d3448873ced3
17 from __future__ import unicode_literals 17 from __future__ import unicode_literals
18 18
19 import os 19 import os
20 import base64 20 import base64
21 21
22 from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication 22 from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication, QUrl
23 23
24 import Preferences 24 import Preferences
25 import Utilities 25 import Utilities
26 26
27 from .SafeBrowsingAPIClient import SafeBrowsingAPIClient 27 from .SafeBrowsingAPIClient import SafeBrowsingAPIClient
28 from .SafeBrowsingCache import SafeBrowsingCache, ThreatList, HashPrefixList 28 from .SafeBrowsingCache import SafeBrowsingCache, ThreatList, HashPrefixList
29 from .SafeBrowsingUrl import SafeBrowsingUrl
30 from .SafeBrowsingUtilities import toHex
29 31
30 32
31 class SafeBrowsingManager(QObject): 33 class SafeBrowsingManager(QObject):
32 """ 34 """
33 Class implementing the interface for Google Safe Browsing. 35 Class implementing the interface for Google Safe Browsing.
45 """ 47 """
46 super(SafeBrowsingManager, self).__init__() 48 super(SafeBrowsingManager, self).__init__()
47 49
48 self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey") 50 self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey")
49 if self.__apiKey: 51 if self.__apiKey:
52 self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
53 parent=self)
54 # TODO: switch these after debugging is finished
50 ## self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, 55 ## self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
51 ## parent=self) 56 ## parent=self,
52 # TODO: switch these after debugging is finished 57 ## fairUse=False)
53 self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
54 parent=self,
55 fairUse=False)
56 else: 58 else:
57 self.__apiClient = None 59 self.__apiClient = None
58 60
59 self.__enabled = ( 61 self.__enabled = (
60 Preferences.getWebBrowser("SafeBrowsingEnabled") and 62 Preferences.getWebBrowser("SafeBrowsingEnabled") and
63 gsbCachePath = os.path.join( 65 gsbCachePath = os.path.join(
64 Utilities.getConfigDir(), "web_browser", "safe_browsing") 66 Utilities.getConfigDir(), "web_browser", "safe_browsing")
65 self.__cache = SafeBrowsingCache(gsbCachePath, self) 67 self.__cache = SafeBrowsingCache(gsbCachePath, self)
66 68
67 self.__gsbDialog = None 69 self.__gsbDialog = None
68 self.__platforms = None # TODO: delete if not needed 70 self.__setPlatforms()
69 71
70 def configurationChanged(self): 72 def configurationChanged(self):
71 """ 73 """
72 Public method to handle changes of the settings. 74 Public method to handle changes of the settings.
73 """ 75 """
76 self.__apiKey = apiKey 78 self.__apiKey = apiKey
77 if self.__apiKey: 79 if self.__apiKey:
78 if self.__apiClient: 80 if self.__apiClient:
79 self.__apiClient.setApiKey(self.__apiKey) 81 self.__apiClient.setApiKey(self.__apiKey)
80 else: 82 else:
83 self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
84 parent=self)
85 # TODO: switch these after debugging is finished
81 ## self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, 86 ## self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
82 ## parent=self) 87 ## parent=self,
83 # TODO: switch these after debugging is finished 88 ## fairUse=False)
84 self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
85 parent=self,
86 fairUse=False)
87 89
88 self.__enabled = ( 90 self.__enabled = (
89 Preferences.getWebBrowser("SafeBrowsingEnabled") and 91 Preferences.getWebBrowser("SafeBrowsingEnabled") and
90 bool(self.__apiKey)) 92 bool(self.__apiKey))
93
94 self.__setPlatforms()
95
96 def __setPlatforms(self):
97 """
98 Private method to set the platforms to be checked against.
99 """
100 self.__platforms = None
101 if Preferences.getWebBrowser("SafeBrowsingFilterPlatform"):
102 if Utilities.isWindowsPlatform():
103 platform = "windows"
104 elif Utilities.isMacPlatform():
105 platform = "macos"
106 else:
107 # treat all other platforms like linux
108 platform = "linux"
109 self.__platforms = SafeBrowsingAPIClient.getPlatformTypes(platform)
91 110
92 def isEnabled(self): 111 def isEnabled(self):
93 """ 112 """
94 Public method to check, if safe browsing is enabled. 113 Public method to check, if safe browsing is enabled.
95 114
123 if not self.__enabled: 142 if not self.__enabled:
124 return False, self.tr("Safe Browsing is disabled.") 143 return False, self.tr("Safe Browsing is disabled.")
125 144
126 if not self.__apiClient.fairUseDelayExpired(): 145 if not self.__apiClient.fairUseDelayExpired():
127 return False, \ 146 return False, \
128 self.tr("The fair use wait period has not expired yet.") 147 self.tr("The fair use wait period has not expired yet."
148 "Expiration will be at {0}.").format(
149 self.__apiClient.getFairUseDelayExpirationDateTime()
150 .toString("yyyy-MM-dd, HH:mm:ss"))
129 151
130 # step 1: remove expired hashes 152 # step 1: remove expired hashes
131 self.__cache.cleanupFullHashes() 153 self.__cache.cleanupFullHashes()
132 154
133 # step 2: update threat lists 155 # step 2: update threat lists
204 226
205 def __verifyThreatListChecksum(self, threatList, remoteChecksum): 227 def __verifyThreatListChecksum(self, threatList, remoteChecksum):
206 """ 228 """
207 Private method to verify the local checksum of a threat list with the 229 Private method to verify the local checksum of a threat list with the
208 checksum of the safe browsing server. 230 checksum of the safe browsing server.
231
232 @param threatList threat list to calculate checksum for
233 @type ThreatList
234 @param remoteChecksum SHA256 checksum as reported by the Google server
235 @type bytes
236 @return flag indicating equality
237 @rtype bool
209 """ 238 """
210 localChecksum = self.__cache.hashPrefixListChecksum(threatList) 239 localChecksum = self.__cache.hashPrefixListChecksum(threatList)
211 return remoteChecksum == localChecksum 240 return remoteChecksum == localChecksum
212 241
213 def fullCacheCleanup(self): 242 def fullCacheCleanup(self):
225 from .SafeBrowsingDialog import SafeBrowsingDialog 254 from .SafeBrowsingDialog import SafeBrowsingDialog
226 self.__gsbDialog = SafeBrowsingDialog( 255 self.__gsbDialog = SafeBrowsingDialog(
227 self, parent=WebBrowserWindow.mainWindow()) 256 self, parent=WebBrowserWindow.mainWindow())
228 257
229 self.__gsbDialog.show() 258 self.__gsbDialog.show()
259
260 def lookupUrl(self, url):
261 """
262 Public method to lookup an URL.
263
264 @param url URL to be checked
265 @type str or QUrl
266 @return list of threat lists the URL was found in
267 @rtype list of ThreatList
268 @exception ValueError raised for an invalid URL
269 """
270 if self.__enabled:
271 if isinstance(url, QUrl):
272 urlStr = url.toString().strip()
273 else:
274 urlStr = url.strip()
275
276 if not urlStr:
277 raise ValueError("Empty URL given.")
278
279 urlHashes = SafeBrowsingUrl(urlStr).hashes()
280 listNames = self.__lookupHashes(urlHashes)
281 if listNames:
282 return listNames
283
284 return None
285
286 def __lookupHashes(self, fullHashes):
287 """
288 Private method to lookup the given hashes.
289
290 @param fullHashes list of hashes to lookup
291 @type list of bytes
292 @return names of threat lists hashes were found in
293 @rtype list of ThreatList
294 """
295 fullHashes = list(fullHashes)
296 cues = [toHex(fh[:4]) for fh in fullHashes]
297 result = []
298
299 matchingPrefixes = {}
300 matchingFullHashes = set()
301 isPotentialThreat = False
302 # Lookup hash prefixes which match full URL hash
303 for threatList, hashPrefix, negativeCacheExpired in \
304 self.__cache.lookupHashPrefix(cues):
305 for fullHash in fullHashes:
306 if fullHash.startswith(hashPrefix):
307 isPotentialThreat = True
308 # consider hash prefix negative cache as expired if it
309 # is expired in at least one threat list
310 matchingPrefixes[hashPrefix] = matchingPrefixes.get(
311 hashPrefix, False) or negativeCacheExpired
312 matchingFullHashes.add(fullHash)
313
314 # if none matches, url hash is clear
315 if not isPotentialThreat:
316 return []
317
318 # if there is non-expired full hash, URL is blacklisted
319 matchingExpiredThreatLists = set()
320 for threatList, hasExpired in self.__cache.lookupFullHashes(
321 matchingFullHashes):
322 if hasExpired:
323 matchingExpiredThreatLists.add(threatList)
324 else:
325 result.append(threatList)
326 if result:
327 return result
328
329 # If there are no matching expired full hash entries and negative
330 # cache is still current for all prefixes, consider it safe.
331 if len(matchingExpiredThreatLists) == 0 and \
332 sum(map(int, matchingPrefixes.values())) == 0:
333 return []
334
335 # Now it can be assumed that there are expired matching full hash
336 # entries and/or cache prefix entries with expired negative cache.
337 # Both require full hash synchronization.
338 self.__syncFullHashes(matchingPrefixes.keys())
339
340 # Now repeat full hash lookup
341 for threatList, hasExpired in self.__cache.lookupFullHashes(
342 matchingFullHashes):
343 if not hasExpired:
344 result.append(threatList)
345
346 return result
347
348 def __syncFullHashes(self, hashPrefixes):
349 """
350 Private method to download full hashes matching given prefixes.
351
352 This also updates the cache expiration timestamps.
353
354 @param hashPrefixes list of hash prefixes to get full hashes for
355 @type list of bytes
356 """
357 threatLists = self.__cache.getThreatLists()
358 clientStates = {}
359 for threatList, clientState in threatLists:
360 clientStates[threatList.asTuple()] = clientState
361
362 fullHashResponses = self.__apiClient.getFullHashes(
363 hashPrefixes, clientStates)
364
365 # update negative cache for each hash prefix
366 # store full hash with positive cache bumped up
367 for match in fullHashResponses["matches"]:
368 threatList = ThreatList.fromApiEntry(match)
369 hashValue = base64.b64decode(match["threat"]["hash"])
370 cacheDuration = int(match["cacheDuration"].rstrip("s"))
371 malwareThreatType = None
372 for metadata in match["threatEntryMetadata"].get("entries", []):
373 key = base64.b64decode(metadata["key"])
374 value = base64.b64decode(metadata["value"])
375 if key == b"malware_threat_type":
376 malwareThreatType = value
377 if not isinstance(malwareThreatType, str):
378 malwareThreatType = malwareThreatType.decode()
379 self.__cache.storeFullHash(threatList, hashValue, cacheDuration,
380 malwareThreatType)
381
382 negativeCacheDuration = int(
383 fullHashResponses["negativeCacheDuration"].rstrip("s"))
384 for prefixValue in hashPrefixes:
385 for threatList, clientState in threatLists:
386 self.__cache.updateHashPrefixExpiration(
387 threatList, prefixValue, negativeCacheDuration)
388
389 @classmethod
390 def getIgnoreSchemes(cls):
391 """
392 Class method to get the schemes not to be checked.
393
394 @return list of schemes to be ignored
395 @rtype list of str
396 """
397 return [
398 "about",
399 "eric",
400 "qrc",
401 "qthelp",
402 "chrome",
403 "abp",
404 "file",
405 ]
406
407 def getThreatMessage(self, threatType):
408 """
409 Public method to get a warning message for the given threat type.
410
411 @param threatType threat type to get the message for
412 @type str
413 @return threat message
414 @rtype str
415 """
416 if self.__apiClient:
417 msg = self.__apiClient.getThreatMessage(threatType)
418 else:
419 msg = ""
420
421 return msg
422
423 def getThreatMessages(self, threatLists):
424 """
425 Public method to get threat messages for the given threats.
426
427 @param threatLists list of threat lists to get a message for
428 @type list of ThreatList
429 @return list of threat messages, one per unique threat type
430 @rtype list of str
431 """
432 threatTypes = set()
433 for threatList in threatLists:
434 threatTypes.add(threatList.threatType)
435
436 messages = []
437 if self.__apiClient:
438 for threatType in sorted(threatTypes):
439 msg = self.__apiClient.getThreatMessage(threatType)
440 messages.append(msg)
441
442 return messages
443
444 def getThreatType(self, threatList):
445 """
446 Public method to get a display string for a given threat type.
447
448 @param threatList threat list to get display string for
449 @type str
450 @return display string
451 @rtype str
452 """
453 displayString = ""
454 if self.__apiClient:
455 displayString = self.__apiClient.getThreatType(
456 threatList.threatType)
457 return displayString
458
459 def getPlatformString(self, platformType):
460 """
461 Public method to get the platform string for a given platform type.
462
463 @param platformType platform type as defined in the v4 API
464 @type str
465 @return platform string
466 @rtype str
467 """
468 if self.__apiClient:
469 return self.__apiClient.getPlatformString(platformType)
470 else:
471 return ""
472
473 def getThreatEntryString(self, threatEntry):
474 """
475 Public method to get the threat entry string.
476
477 @param threatEntry threat entry type as defined in the v4 API
478 @type str
479 @return threat entry string
480 @rtype str
481 """
482 if self.__apiClient:
483 return self.__apiClient.getThreatEntryString(threatEntry)
484 else:
485 return ""

eric ide

mercurial