ContentFilterPlugin.py 10 KB

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