ContentFilterPlugin.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import time
  2. import re
  3. import cgi
  4. import hashlib
  5. from Plugin import PluginManager
  6. from Translate import Translate
  7. from Config import config
  8. from ContentFilterStorage import ContentFilterStorage
  9. if "_" not in locals():
  10. _ = Translate("plugins/ContentFilter/languages/")
  11. @PluginManager.registerTo("SiteManager")
  12. class SiteManagerPlugin(object):
  13. def load(self, *args, **kwargs):
  14. global filter_storage
  15. super(SiteManagerPlugin, self).load(*args, **kwargs)
  16. filter_storage = ContentFilterStorage(site_manager=self)
  17. @PluginManager.registerTo("UiWebsocket")
  18. class UiWebsocketPlugin(object):
  19. # Mute
  20. def cbMuteAdd(self, to, auth_address, cert_user_id, reason):
  21. filter_storage.file_content["mutes"][auth_address] = {
  22. "cert_user_id": cert_user_id, "reason": reason, "source": self.site.address, "date_added": time.time()
  23. }
  24. filter_storage.save()
  25. filter_storage.changeDbs(auth_address, "remove")
  26. self.response(to, "ok")
  27. def actionMuteAdd(self, to, auth_address, cert_user_id, reason):
  28. if "ADMIN" in self.getPermissions(to):
  29. self.cbMuteAdd(to, auth_address, cert_user_id, reason)
  30. else:
  31. self.cmd(
  32. "confirm",
  33. [_["Hide all content from <b>%s</b>?"] % cgi.escape(cert_user_id), _["Mute"]],
  34. lambda (res): self.cbMuteAdd(to, auth_address, cert_user_id, reason)
  35. )
  36. def cbMuteRemove(self, to, auth_address):
  37. del filter_storage.file_content["mutes"][auth_address]
  38. filter_storage.save()
  39. filter_storage.changeDbs(auth_address, "load")
  40. self.response(to, "ok")
  41. def actionMuteRemove(self, to, auth_address):
  42. if "ADMIN" in self.getPermissions(to):
  43. self.cbMuteRemove(to, auth_address)
  44. else:
  45. self.cmd(
  46. "confirm",
  47. [_["Unmute <b>%s</b>?"] % cgi.escape(filter_storage.file_content["mutes"][auth_address]["cert_user_id"]), _["Unmute"]],
  48. lambda (res): self.cbMuteRemove(to, auth_address)
  49. )
  50. def actionMuteList(self, to):
  51. if "ADMIN" in self.getPermissions(to):
  52. self.response(to, filter_storage.file_content["mutes"])
  53. else:
  54. return self.response(to, {"error": "Forbidden: Only ADMIN sites can list mutes"})
  55. # Siteblock
  56. def actionSiteblockAdd(self, to, site_address, reason=None):
  57. if "ADMIN" not in self.getPermissions(to):
  58. return self.response(to, {"error": "Forbidden: Only ADMIN sites can add to blocklist"})
  59. filter_storage.file_content["siteblocks"][site_address] = {"date_added": time.time(), "reason": reason}
  60. filter_storage.save()
  61. self.response(to, "ok")
  62. def actionSiteblockRemove(self, to, site_address):
  63. if "ADMIN" not in self.getPermissions(to):
  64. return self.response(to, {"error": "Forbidden: Only ADMIN sites can remove from blocklist"})
  65. del filter_storage.file_content["siteblocks"][site_address]
  66. filter_storage.save()
  67. self.response(to, "ok")
  68. def actionSiteblockList(self, to):
  69. if "ADMIN" in self.getPermissions(to):
  70. self.response(to, filter_storage.file_content["siteblocks"])
  71. else:
  72. return self.response(to, {"error": "Forbidden: Only ADMIN sites can list blocklists"})
  73. # Include
  74. def actionFilterIncludeAdd(self, to, inner_path, description=None, address=None):
  75. if address:
  76. if "ADMIN" not in self.getPermissions(to):
  77. return self.response(to, {"error": "Forbidden: Only ADMIN sites can manage different site include"})
  78. site = self.server.sites[address]
  79. else:
  80. address = self.site.address
  81. site = self.site
  82. if "ADMIN" in self.getPermissions(to):
  83. self.cbFilterIncludeAdd(to, True, address, inner_path, description)
  84. else:
  85. content = site.storage.loadJson(inner_path)
  86. title = _["New shared global content filter: <b>%s</b> (%s sites, %s users)"] % (
  87. cgi.escape(inner_path), len(content.get("siteblocks", {})), len(content.get("mutes", {}))
  88. )
  89. self.cmd(
  90. "confirm",
  91. [title, "Add"],
  92. lambda (res): self.cbFilterIncludeAdd(to, res, address, inner_path, description)
  93. )
  94. def cbFilterIncludeAdd(self, to, res, address, inner_path, description):
  95. if not res:
  96. self.response(to, res)
  97. return False
  98. filter_storage.includeAdd(address, inner_path, description)
  99. self.response(to, "ok")
  100. def actionFilterIncludeRemove(self, to, inner_path, address=None):
  101. if address:
  102. if "ADMIN" not in self.getPermissions(to):
  103. return self.response(to, {"error": "Forbidden: Only ADMIN sites can manage different site include"})
  104. else:
  105. address = self.site.address
  106. key = "%s/%s" % (address, inner_path)
  107. if key not in filter_storage.file_content["includes"]:
  108. self.response(to, {"error": "Include not found"})
  109. filter_storage.includeRemove(address, inner_path)
  110. self.response(to, "ok")
  111. def actionFilterIncludeList(self, to, all_sites=False, filters=False):
  112. if all_sites and "ADMIN" not in self.getPermissions(to):
  113. return self.response(to, {"error": "Forbidden: Only ADMIN sites can list all sites includes"})
  114. back = []
  115. includes = filter_storage.file_content.get("includes", {}).values()
  116. for include in includes:
  117. if not all_sites and include["address"] != self.site.address:
  118. continue
  119. if filters:
  120. include = dict(include) # Don't modify original file_content
  121. include_site = filter_storage.site_manager.get(include["address"])
  122. if not include_site:
  123. continue
  124. try:
  125. content = include_site.storage.loadJson(include["inner_path"])
  126. include["error"] = None
  127. except Exception as err:
  128. if include_site.settings["own"]:
  129. include_site.log.warning("Error loading filter %s: %s" % (include["inner_path"], err))
  130. content = {}
  131. include["error"] = str(err)
  132. include["mutes"] = content.get("mutes", {})
  133. include["siteblocks"] = content.get("siteblocks", {})
  134. back.append(include)
  135. self.response(to, back)
  136. @PluginManager.registerTo("SiteStorage")
  137. class SiteStoragePlugin(object):
  138. def updateDbFile(self, inner_path, file=None, cur=None):
  139. if file is not False: # File deletion always allowed
  140. # Find for bitcoin addresses in file path
  141. matches = re.findall("/(1[A-Za-z0-9]{26,35})/", inner_path)
  142. # Check if any of the adresses are in the mute list
  143. for auth_address in matches:
  144. if filter_storage.isMuted(auth_address):
  145. self.log.debug("Mute match: %s, ignoring %s" % (auth_address, inner_path))
  146. return False
  147. return super(SiteStoragePlugin, self).updateDbFile(inner_path, file=file, cur=cur)
  148. def onUpdated(self, inner_path, file=None):
  149. file_path = "%s/%s" % (self.site.address, inner_path)
  150. if file_path in filter_storage.file_content["includes"]:
  151. self.log.debug("Filter file updated: %s" % inner_path)
  152. filter_storage.includeUpdateAll()
  153. return super(SiteStoragePlugin, self).onUpdated(inner_path, file=file)
  154. @PluginManager.registerTo("UiRequest")
  155. class UiRequestPlugin(object):
  156. def actionWrapper(self, path, extra_headers=None):
  157. match = re.match("/(?P<address>[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path)
  158. if not match:
  159. return False
  160. address = match.group("address")
  161. if self.server.site_manager.get(address): # Site already exists
  162. return super(UiRequestPlugin, self).actionWrapper(path, extra_headers)
  163. if self.server.site_manager.isDomain(address):
  164. address = self.server.site_manager.resolveDomain(address)
  165. if address:
  166. address_sha256 = "0x" + hashlib.sha256(address).hexdigest()
  167. else:
  168. address_sha256 = None
  169. if filter_storage.isSiteblocked(address) or filter_storage.isSiteblocked(address_sha256):
  170. site = self.server.site_manager.get(config.homepage)
  171. if not extra_headers:
  172. extra_headers = {}
  173. script_nonce = self.getScriptNonce()
  174. self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce)
  175. return iter([super(UiRequestPlugin, self).renderWrapper(
  176. site, path, "uimedia/plugins/contentfilter/blocklisted.html?address=" + address,
  177. "Blacklisted site", extra_headers, show_loadingscreen=False, script_nonce=script_nonce
  178. )])
  179. else:
  180. return super(UiRequestPlugin, self).actionWrapper(path, extra_headers)
  181. def actionUiMedia(self, path, *args, **kwargs):
  182. if path.startswith("/uimedia/plugins/contentfilter/"):
  183. file_path = path.replace("/uimedia/plugins/contentfilter/", "plugins/ContentFilter/media/")
  184. return self.actionFile(file_path)
  185. else:
  186. return super(UiRequestPlugin, self).actionUiMedia(path)