diff -r 69fa45e95673 -r b610cb5b501a WebBrowser/SafeBrowsing/SafeBrowsingManager.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/SafeBrowsing/SafeBrowsingManager.py Sat Jul 29 19:41:16 2017 +0200 @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the interface for Google Safe Browsing. +""" + +# +# Some part of this code were ported from gglsbl.client and adapted +# to Qt. +# +# https://github.com/afilipovich/gglsbl +# + +from __future__ import unicode_literals + +import os +import base64 + +from PyQt5.QtCore import QObject + +import Preferences +import Utilities + +from .SafeBrowsingAPIClient import SafeBrowsingAPIClient +from .SafeBrowsingCache import SafeBrowsingCache, ThreatList, HashPrefixList + + +class SafeBrowsingManager(QObject): + """ + Class implementing the interface for Google Safe Browsing. + """ + def __init__(self): + """ + Constructor + """ + super(SafeBrowsingManager, self).__init__() + + self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey") + if self.__apiKey: +## self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, +## parent=self) + # TODO: switch these after debugging is finished + self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, + parent=self, + fairUse=False) + else: + self.__apiClient = None + + self.__enabled = ( + Preferences.getWebBrowser("SafeBrowsingEnabled") and + bool(self.__apiKey)) + + gsbCachePath = os.path.join( + Utilities.getConfigDir(), "web_browser", "safe_browsing") + self.__cache = SafeBrowsingCache(gsbCachePath, self) + + self.__gsbDialog = None + self.__platforms = None # TODO: delete if not needed + + def configurationChanged(self): + """ + Public method to handle changes of the settings. + """ + apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey") + if apiKey != self.__apiKey: + self.__apiKey = apiKey + if self.__apiKey: + if self.__apiClient: + self.__apiClient.setApiKey(self.__apiKey) + else: +## self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, +## parent=self) + # TODO: switch these after debugging is finished + self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, + parent=self, + fairUse=False) + + self.__enabled = ( + Preferences.getWebBrowser("SafeBrowsingEnabled") and + bool(self.__apiKey)) + + def isEnabled(self): + """ + Public method to check, if safe browsing is enabled. + + @return flag indicating the enabled state + @rtype bool + """ + return self.__enabled + + def close(self): + """ + Public method to close the safe browsing interface. + """ + self.__cache.close() + + def fairUseDelayExpired(self): + """ + Public method to check, if the fair use wait period has expired. + + @return flag indicating expiration + @rtype bool + """ + return self.__enabled and self.__apiClient.fairUseDelayExpired() + + def updateHashPrefixCache(self): + """ + Public method to load or update the locally cached threat lists. + + @return flag indicating success and an error message + @rtype tuple of (bool, str) + """ + if not self.__enabled: + return False, self.tr("Safe Browsing is disabled.") + + if not self.__apiClient.fairUseDelayExpired(): + return False, \ + self.tr("The fair use wait period has not expired yet.") + + # step 1: remove expired hashes + self.__cache.cleanupFullHashes() + + # step 2: update threat lists + threatListsForRemove = {} + for threatList, clientState in self.__cache.getThreatLists(): + threatListsForRemove[repr(threatList)] = threatList + threatLists = self.__apiClient.getThreatLists() + for entry in threatLists: + threatList = ThreatList.fromApiEntry(entry) + if self.__platforms is None or \ + threatList.platformType in self.__platforms: + self.__cache.addThreatList(threatList) + key = repr(threatList) + if key in threatListsForRemove: + del threatListsForRemove[key] + for threatList in threatListsForRemove.values(): + self.__cache.deleteHashPrefixList(threatList) + self.__cache.deleteThreatList(threatList) + del threatListsForRemove + + # step 3: update threats + threatLists = self.__cache.getThreatLists() + clientStates = {} + for threatList, clientState in threatLists: + clientStates[threatList.asTuple()] = clientState + threatsUpdateResponses = \ + self.__apiClient.getThreatsUpdate(clientStates) + for response in threatsUpdateResponses: + responseThreatList = ThreatList.fromApiEntry(response) + if response["responseType"] == "FULL_UPDATE": + self.__cache.deleteHashPrefixList(responseThreatList) + for removal in response.get("removals", []): + self.__cache.removeHashPrefixIndices( + responseThreatList, removal["rawIndices"]["indices"]) + for addition in response.get("additions", []): + hashPrefixList = HashPrefixList( + addition["rawHashes"]["prefixSize"], + base64.b64decode(addition["rawHashes"]["rawHashes"])) + self.__cache.populateHashPrefixList(responseThreatList, + hashPrefixList) + expectedChecksum = base64.b64decode(response["checksum"]["sha256"]) + if self.__verifyThreatListChecksum(responseThreatList, + expectedChecksum): + self.__cache.updateThreatListClientState( + responseThreatList, response["newClientState"]) + else: + return False, \ + self.tr("Local cache checksum does not match the server." + " Consider cleaning the cache. Threat update has" + " been aborted.") + + return True, "" + + def __verifyThreatListChecksum(self, threatList, remoteChecksum): + """ + Private method to verify the local checksum of a threat list with the + checksum of the safe browsing server. + """ + localChecksum = self.__cache.hashPrefixListChecksum(threatList) + return remoteChecksum == localChecksum + + def fullCacheCleanup(self): + """ + Public method to clean up the cache completely. + """ + self.__cache.prepareCacheDb() + + def showSafeBrowsingDialog(self): + """ + Public slot to show the safe browsing management dialog. + """ + if self.__gsbDialog is None: + from WebBrowser.WebBrowserWindow import WebBrowserWindow + from .SafeBrowsingDialog import SafeBrowsingDialog + self.__gsbDialog = SafeBrowsingDialog( + self, parent=WebBrowserWindow.mainWindow()) + + self.__gsbDialog.show()