ContentFilterPlugin.py 10 KB

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